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.