Protecting EJB web services with XACML, A beginners tutorial

    The intent of this tutorial is to provide a basic example of a very simple EBJ protected via a XACML authorization policy.

     

    System Configuration

    This tutorial assumes the following system configuration.

    JBoss - 5.1.0GA

    Java - 1.6

    JBossWS - 3.3.1GA native (installed with ant -Djboss.server.instance=default deploy-jboss510)


    Creating a Simple EJB Service

     

    All files, including an Eclipse project file are located in the zip attached to this article. The zip includes an ant (1.7+) build script that has targets

    for building and deploying the service and also a target for executing the client test cases.

     

    For the purposes of this tutorial, a simple web service will be created. The web service will consist of two files, an interface and an implementation.

    The webservice is derived from the SecureEndpoint example webservice located in the JBossWS examples.

     

    SecureEndpoint.java

     

    The SecureEndpoint interface defines a simple webservice with one method: echo. For more information on the annotations used to define the

    webservice, see the JBossWS wiki.

     

    package org.example.xacml.svc;
     
    import javax.jws.WebMethod;
    import javax.jws.WebParam;
    import javax.jws.WebResult;
    import javax.jws.WebService;
    import javax.jws.soap.SOAPBinding;
    import javax.jws.soap.SOAPBinding.Style;
     
    @WebService(name = "SecureEndpointService", targetNamespace = "http://org.example.xacml.svc/sampleDomain")
    @SOAPBinding(style = Style.RPC)
    public interface SecureEndpoint {
    @WebMethod
    @WebResult(targetNamespace = "http://org.example.xacml.svc/sampleDomain", partName = "return")
    public String echo(@WebParam(name = "arg0", partName = "arg0") String arg0);
    }
    

     

    SecureEndpointImpl.java

    The guts of the web service reside in the SecureEndpointImpl.java file. The critical component of this file are the annotation for the security domain: @SecurityDomain("sample-security").  The annotation defines the security domain that we will use for our authentication and authorization and will be defined in configuration files below. The remaining annotations are standard web service annotations.

     

    package org.example.xacml.svc;
    
    import javax.ejb.Stateless;
    import javax.jws.WebMethod;
    import javax.jws.WebService;
    import javax.jws.soap.SOAPBinding;
    import javax.jws.soap.SOAPBinding.Style;
    
    import org.jboss.ejb3.annotation.SecurityDomain;
    import org.jboss.wsf.spi.annotation.AuthMethod;
    import org.jboss.wsf.spi.annotation.TransportGuarantee;
    import org.jboss.wsf.spi.annotation.WebContext;
    
    @Stateless(name = "SecureEndpoint")
    @SOAPBinding(style = Style.RPC)
    @WebService(name = "SecureEndpointService", serviceName = "SecureEndpointService", targetNamespace = "http://org.example.xacml.svc/sampleDomain")
    @WebContext(contextRoot = "/sampleDomain", urlPattern = "/*", authMethod = AuthMethod.BASIC, transportGuarantee = TransportGuarantee.NONE, secureWSDLAccess = false)
    @SecurityDomain("sample-security")
    public class SecureEndpointImpl implements SecureEndpoint {
         @WebMethod
         public String echo(String input) {
              return input;
         }
    }
    

    Now that the simple service is complete, we must create the files required to add XACML authorization support. 


    META-INF Contents

     

    When creating a XACML protected web service, the deployed jar must contain a META-INF directory populated with a few important files.

    jboss-xml

    The jboss-xml file defines the security domain we will be using to protect our service.  In this instance, we define a jaas domain called: sample-security.  It is important to notice this defined domain is used through out the deployment, such as in our @SecurityDomain annotation in our implementation above.

     

    <?xml version="1.0"?>
    <!DOCTYPE jboss PUBLIC
          "-//JBoss//DTD JBOSS 5.0//EN"
          "http://www.jboss.org/j2ee/dtd/jboss_5_0.dtd">
    <jboss>
       <security-domain>java:/jaas/sample-security</security-domain>
    </jboss>
    
    

     

    jbossxacml-config.xml

    Now on to setting up our XACML policy.  JBoss XACML requires a configuration file located within the META-INF directory that indicated where and how your application policies are located. It is important to remember this this file must be named jbossxacml-config.xml, otherwise it will not get found by the system. In our example, we define a single policy, also located in META-INF called xacml-policy.xml.  We also use the standard Locators, JBossPolicySetLocator, and JBossPolicyLocator. Given that we only have a policy defined, we could probably get away with a single PolicyLocator, however having the PolicySetLocator does not introduce any issues.

     

    <ns:jbosspdp xmlns:ns="urn:jboss:xacml:2.0">
      <ns:Policies>
        <ns:Policy>
           <ns:Location>META-INF/xacml-policy.xml</ns:Location>
         </ns:Policy>
      </ns:Policies>
      <ns:Locators>
        <ns:Locator Name="org.jboss.security.xacml.locators.JBossPolicySetLocator"/> 
        <ns:Locator Name="org.jboss.security.xacml.locators.JBossPolicyLocator"/> 
      </ns:Locators>
    </ns:jbosspdp>
    
    
    

     

    xacml-policy.xml

    When creating the actual xacml policy, a few extra resources and actions are required to properly and successfully protect a service.

    In the policy below, we create a policy that only allows bob, with a role of role1 to access the echo web service, defined as an action in the policy. While no real policy would ever contain a subject match rule for a specific user, such as bob in our policy, we leave this policy requirement for learning purposes.

     

    It is also important that our policy provide authorization for our web services contextRoot:  /sampleDomain, and the endpoint: SecureEndpoint.

    We must also provide a write action in our policy. (Debugging Tip: If your policy fails to provide authorization for a case you expected a PERMIT, turn on TRACE level debugging and compare the XACML request attributes with the policy.  It is often the case that you are not placing a rule for the resource in the policy.)

     

    <?xml version="1.0" encoding="UTF-8"?>
    <Policy xmlns="urn:oasis:names:tc:xacml:2.0:policy:schema:os"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="urn:oasis:names:tc:xacml:2.0:policy:schema:os
        access_control-xacml-2.0-policy-schema-os.xsd"
        PolicyId="urn:oasis:names:tc:xacml:2.0:jboss-test:XV:policy"
        RuleCombiningAlgId="urn:oasis:names:tc:xacml:1.0:rule-combining-algorithm:deny-overrides">
      <Description>Sample policy</Description>
      <Target />
      <Rule RuleId="urn:oasis:names:tc:xacml:2.0:jboss-test:XVI:rule" Effect="Permit">
        <Description/>
        <Target>
        <Subjects>
          <Subject>
            <SubjectMatch MatchId="urn:oasis:names:tc:xacml:1.0:function:string-equal">
              <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">bob</AttributeValue>
              <SubjectAttributeDesignator AttributeId="urn:oasis:names:tc:xacml:1.0:subject:subject-id"
                  DataType="http://www.w3.org/2001/XMLSchema#string" />
            </SubjectMatch>
            
            <SubjectMatch MatchId="urn:oasis:names:tc:xacml:1.0:function:string-equal">
              <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">role1</AttributeValue>
              <SubjectAttributeDesignator AttributeId="urn:oasis:names:tc:xacml:2.0:subject:role" 
                 DataType="http://www.w3.org/2001/XMLSchema#string" />
            </SubjectMatch>
          </Subject>
        </Subjects>
        <Resources>
          <Resource>
            <ResourceMatch MatchId="urn:oasis:names:tc:xacml:1.0:function:anyURI-equal">
              <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#anyURI">/sampleDomain</AttributeValue>
              <ResourceAttributeDesignator AttributeId="urn:oasis:names:tc:xacml:1.0:resource:resource-id"
                 DataType="http://www.w3.org/2001/XMLSchema#anyURI" />
            </ResourceMatch>
          </Resource>
        <Resource>
          <ResourceMatch MatchId="urn:oasis:names:tc:xacml:1.0:function:string-equal">
            <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">SecureEndpoint</AttributeValue>
            <ResourceAttributeDesignator AttributeId="urn:oasis:names:tc:xacml:1.0:resource:resource-id"
              DataType="http://www.w3.org/2001/XMLSchema#string" />
          </ResourceMatch>
        </Resource>
      </Resources>
      <Actions>
        <Action>
          <ActionMatch MatchId="urn:oasis:names:tc:xacml:1.0:function:string-equal">
            <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">write</AttributeValue>
            <ActionAttributeDesignator AttributeId="urn:oasis:names:tc:xacml:1.0:action:action-id"
              DataType="http://www.w3.org/2001/XMLSchema#string" />
          </ActionMatch>
        </Action>
        <Action>
          <ActionMatch MatchId="urn:oasis:names:tc:xacml:1.0:function:string-equal">
            <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">echo</AttributeValue>
            <ActionAttributeDesignator AttributeId="urn:oasis:names:tc:xacml:1.0:action:action-id"
               DataType="http://www.w3.org/2001/XMLSchema#string" />
          </ActionMatch>
        </Action>
      </Actions>
      </Target>
      </Rule>
    </Policy>
    
    

     

    Jar Resources

    The following resources are contained within the jar, but NOT within the META-INF directory.

    sample-jboss-beans.xml

    The sample-jboss-beans file defines the login modules used for our service. In this service, we are using a UsersRolesLoginModule and a XACMLAuthorizationModule.

     

    The UsersRolesLoginModule requires the location of files containing the list of allowed users and a file defining the roles associated with the users.

    Notice the name of the application-policy sample-security, is the same name as the security domain name defined in the java class and in jboss.xml.

     

    <?xml version="1.0" encoding="UTF-8"?>
    
    <deployment xmlns="urn:jboss:bean-deployer:2.0">
      <application-policy xmlns="urn:jboss:security-beans:1.0" name="sample-security">
        <authentication>
          <login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule" flag="required">
            <module-option name="usersProperties">sample-users.properties</module-option>
            <module-option name="rolesProperties">sample-roles.properties</module-option>
          </login-module>
        </authentication>
        <authorization>
          <policy-module code="org.jboss.security.authorization.modules.XACMLAuthorizationModule" flag="required" />
        </authorization>
      </application-policy>
    </deployment>
    
    
    

     

    sample-users.properties

    The sample-users properties file is a simple java properties file defining name/value pairs.

     

    bob=bob
    alice=alice
    
    

     

    sample-roles.properties

    The sample-roles properties file is also a simple name/value pair.

    
    bob=role1
    alice=role2
    
    
    

     

     

    Test Client

    To test our simple service, we will use a simple JUnit test case. In our test case, we have three tests, a test to verify that we do not permit anonymous access to our service, a test to verify a non permitted user does not have access and finally a test for the proper user. When run successfully, all three tests should pass.

     

    package org.example.xacml.client;
    
    import static org.junit.Assert.assertEquals;
    import static org.junit.Assert.fail;
    
    import java.net.URL;
    import java.util.Map;
    
    import javax.xml.namespace.QName;
    import javax.xml.ws.BindingProvider;
    import javax.xml.ws.Service;
    import javax.xml.ws.WebServiceException;
    
    import org.example.xacml.svc.SecureEndpoint;
    import org.junit.Test;
    
    public class TestClient {
      public final String TARGET_ENDPOINT_ADDRESS = "http://localhost:8080/sampleDomain";
    
      private SecureEndpoint getPort() throws Exception {
        URL wsdlURL = new URL(TARGET_ENDPOINT_ADDRESS + "?wsdl");
        QName serviceName = new QName("http://org.example.xacml.svc/sampleDomain", "SecureEndpointService");
        SecureEndpoint port = Service.create(wsdlURL, serviceName).getPort(SecureEndpoint.class);
        return port;
      }
    
      @Test
      public void testNegativeA() throws Exception {
        SecureEndpoint port = getPort();
        try {
          port.echo("Hello");
          fail("Expected: Invalid HTTP server response [401] - Unauthorized");
        } catch (WebServiceException ex) {
          // all good
        }
      }
    
      @Test
      public void testNegativeB() throws Exception {
        SecureEndpoint port = getPort();
    
        Map<String, Object> reqContext = ((BindingProvider) port).getRequestContext();
        reqContext.put(BindingProvider.USERNAME_PROPERTY, "alice");
        reqContext.put(BindingProvider.PASSWORD_PROPERTY, "alice");
              
        try {
          port.echo("Hello");
          fail("Expected: Invalid HTTP server response [401] - Unauthorized");
        } catch (WebServiceException ex) {
          // all good
        }
      }
    
      @Test
      public void testPositiveEcho() throws Exception {
        SecureEndpoint port = getPort();
    
        Map<String, Object> reqContext = ((BindingProvider) port).getRequestContext();
        reqContext.put(BindingProvider.USERNAME_PROPERTY, "bob");
        reqContext.put(BindingProvider.PASSWORD_PROPERTY, "bob");
    
        String retObj = port.echo("Hello");
        assertEquals("Hello", retObj);
      }
    }
    

     

    Running the Service and the Tests

    To execute the service and the tests, download the attached tar file and unzip to your local workspace.

    Edit the file build.properties and set the location of your JBoss instance.

     

    Assuming you have ANT 1.7+, and JBoss is currently running, execute the following ant targets:

     

    ant deploy-sample-endpoint

     

    once the deployment has finished, run the tests

     

    ant run-client

     

    The junit target should report that all three tests passed.

     

    Potential Issues

    When deploying a service, I have frequently encountered a issue where the tests will fail because the server is reporting a null PolicyRegistration. This appears to be an bug with JBossPDP not being Serializable. When this issue occurs, remove any jars from the deploy directory that contain a XACML authorization module and restart JBoss.