ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Separation of Concerns in Web Service Implementations

by Tieu Luu
09/06/2006

Separation of concerns is a core principle of Service-Oriented Architectures. Unfortunately, this principle is often lost when it comes to the implementations of SOA services. All too often we see a big implementation class with multiple concerns such as security, transaction management, and logging all mixed in with the business logic. Using the Spring Framework and principles of Aspect Oriented Programming (AOP), we can drive the separation of concerns down into the implementation of services.

In this article, we show how to develop a Web service using Apache Axis and Spring, and secure it with Acegi Security--all while keeping the concerns nicely separated.

Motivation and Design

The example we will use in this article is a service called FundsTransferService, which a bank might use to transfer funds from one account into another. The WSDL for this service is available along with all the source code, configuration files, and build file in the Resources section of this article. We've deliberately kept this service very simple so that we can focus on the more relevant aspects of the article. In the implementation of this service, we will be dealing with three concerns:

A real system would most likely have to deal with additional concerns such as transaction management, logging, etc.

We want to design an implementation such that the code specifications that handle each concern are cleanly separated from one another. For the web service plumbing, we will use Axis to expose the functionality as a service. The business logic for transferring funds from one account into another is encapsulated in a set of POJOs (Plain Old Java Objects). Security will be provided by the Acegi Security framework. We will use the Spring Framework and its AOP facilities to tie everything together to minimize the dependencies across all of the code that makes up the implementation for this web service.

The design for this implementation is shown in Figure 1. The objects in yellow are the ones that we need to implement. The ones in blue are from Axis; the ones in pink are from Acegi; and the ones in green are from Spring. FundsTransferService is the interface for our service as defined in the WSDL. To simplify the diagram, we've shown all the Axis classes as a component called Axis Engine. The BasicHandler is also an Axis class, but we've shown that separately because it is significant to our design (more on that later). FundsTransferServiceSoapBindingImpl is a generated class from Axis that we need to implement to provide the service functionality. It will delegate to the business logic POJO AccountMgrImpl indirectly through Spring (this will be explained in more detail later as well). AccountMgrImpl is wrapped with an AccountMgr interface because it's good practice, and because it also allows us to insert Spring to do its magic (although there's another way to use Spring without the interface).

Thumbnail, click for full-size image.
Figure 1. Design for the implementation of FundsTransferService (click for full-size image).

Now back to the BasicHandler. The reason this is needed is related to how we've chosen to provide security. In this example, we're dealing with security at two different levels: security for the web service, and security for the application code, i.e., the POJOs. In the spirit of separation of concerns, we've come up with a design that allows us to split this up. Axis allows us to plug in custom handlers that will intercept request and response messages to provide additional functionality such as security. Thus, we will create a custom handler called AcegiBridgeAuthenticationHandler that will be responsible for dealing with web service security. It extends the Axis class BasicHandler so that we can plug it into the Axis handler framework. Acegi will be used to provide the application-level security, i.e., providing access control on the POJOs.

To get both of these to work together seamlessly, we need to bridge the web service security context into the Acegi security context--hence the name AcegiBridgeAuthenticationHandler for our custom Axis handler class. Not only will it handle the web service security processing, it will also be responsible for bridging the security context obtained from that processing into the Acegi environment, so that Acegi can then decide whether or not to grant access to the POJOs. It does this by extracting the security claims out of the web service request message, verifying them, and then creating an authentication token, specifically a UsernamePasswordAuthenticationToken because we've chosen username/password authentication for this example. It then sets this authentication token into the Acegi SecurityContext so that the requester's credentials and permissions are available to Acegi later when it is controlling access on the business logic POJOs.

Now to explain how we use Spring to tie everything together. The magic lies in the little green object in Figure 1 called AOP proxy, which is generated by Spring. This object implements our AccountMgr interface and acts as a proxy to our business logic POJO AccountMgrImpl. This allows Spring to plug in Acegi's MethodSecurityInterceptor to perform access-control checks when someone tries to invoke methods on our POJO. When FundsTransferServiceSoapBindingImpl delegates web service requests to our POJO, it is actually delegating to an instance of the AOP proxy instead of directly to AccountMgrImpl. Because FundsTransferServiceSoapBindingImpl extends ServletEndpointSupport, it can access the Spring application context to get a reference to the AOP proxy, which we've configured with the proper interface, interceptors, and target class, AccountMgrImpl.

The sequence diagram in Figure 2 shows the flow of processing that occurs when a client invokes the FundsTransferService. The request message is received by the Axis Engine, which then invokes the AcegiBridgeAuthenticationHandler. The AcegiBridgeAuthenticationHandler verifies the authentication information and then creates a UsernamePasswordAuthenticationToken. Next, it sets this token into the SecurityContext for Acegi to use later. After the AcegiBridgeAuthenticationHandler successfully returns, the Axis Engine then calls the transferFunds() method on FundsTransferServiceSoapBindingImpl. The FundsTransferServiceSoapBindingImpl delegates this to the AOP proxy, which it obtained earlier during initialization from the Spring web application context. The AOP proxy calls the Acegi MethodSecurityInterceptor so that it can do its security checks. The MethodSecurityInterceptor gets the authentication token from the SecurityContext and checks whether it has already been authenticated. Next, it uses the information in the authentication token to see if the client should be granted access to invoke the transferFunds() method on AccountMgrImpl. If the client is allowed access, then the MethodSecurityInterceptor allows invocation of that method to proceed to the AccountMgrImpl. AccountMgrImpl finally processes that request and returns the results, which are ultimately propagated back to the client program.

Thumbnail, click for full-size image.
Figure 2. Sequence diagram showing processing flow through the service implementation (click for full-size image).

Business Logic Implementation and Configuration

We'll start off by explaining the implementation and configuration for the business logic classes, since they are the simplest. See the source for the AccountMgr interface and the AccountMgrImpl class in the downloadable sample code. As you can see from the source, the implementation doesn't actually do anything, so we can keep things simple since this article's not about how to write code for transferring funds.

Below is a fragment of the Spring configuration file (the entire configuration file is available in the Resources section) showing how we've configured the Spring beans for the business logic so that we can use Spring's AOP facilities. The first bean entry simply sets up a bean for our AccountMgrImpl class. The second bean entry is how we get all of the AOP proxy magic we discussed earlier to work. We set up a bean with id accountMgr that will be obtained from the ProxyFactoryBean. When the FundsTransferServiceSoapBindingImpl class asks Spring for the bean with this id, the ProxyFactoryBean will return an instance of the AOP proxy object. We configure it to implement our AccountMgr interface so that client programs think they're just working with a business logic object. With the second property named interceptorNames, we set it up such that a bean called securityInterceptor (which will be explained later) can intercept method invocations to perform security checks. This allows us to plug in the Acegi security mechanisms without any dependencies in our business logic code. Finally, we set the target to the accountMgrTarget bean so that the method invocations will eventually be propagated to our actual business logic class, AccountMgrImpl.

<beans>
  <bean id="accountMgrTarget"
    class="com.mybank.bizlogic.AccountMgrImpl"/>
  . . .
  <bean id="accountMgr"
    class="org.springframework.aop.framework.
    ProxyFactoryBean">
    <property name="proxyInterfaces">
      <list>
        <value>
          com.mybank.bizlogic.AccountMgr
        </value>
      </list>
    </property>
    <property name="interceptorNames">
      <list>
        <value>
          securityInterceptor
        </value>
      </list>
    </property>
    <property name="target">
      <ref bean="accountMgrTarget"/>
    </property>
  </bean>
  . . .
</beans>

Web Service Implementation and Configuration

The FundsTransferServiceSoapBindingImpl class is the web service implementation. See the source for it in the downloadable sample code. The skeleton for this class is generated by Axis, and we just fill in the methods to provide the implementation. Notice that this class extends ServletEndpointSupport. This is a convenience class provided by Spring that can be used for JAX-RPC web service implementations to obtain a reference to the Spring application context. By extending this class, the FundsTransferServiceSoapBindingImpl class can access the Spring context to obtain a reference to the accountMgr bean described earlier. Since the FundsTransferServiceSoapBindingImpl class is managed by Axis, we can't use Spring's dependency injection facilities to automatically get a reference to that bean. Thus, we have to do it explicitly in the onInit() method. Unfortunately, this adds some dependencies in this class to Spring-specific classes. Oh, well--that's a small price to pay to be able take advantage of the benefits of using Spring and Acegi. Notice that in the actual method transferFunds(), the code just delegates to the accountMgr bean.

In the Axis configuration files (deploy.wsdd and server-config.wsdd), we'll need to make sure that the implementation class for the service is set to this class, FundsTransferServiceSoapBindingImpl, and not the other skeleton class (FundsTransferServiceSoapBindingSkeleton) that is generated by Axis. To get Spring to work properly in the same web application as Axis, we'll need to add the following entries to the web.xml file. The context-param entry specifies where the Spring configuration file is located. The listener entry sets it up so that the Spring configuration and context is loaded up at start up.

<web-app>
  <context-param>
    <param-name>
      contextConfigLocation
    </param-name>
    <param-value>
      /WEB-INF/spring-config.xml
    </param-value>
  </context-param>

  <listener>
    <listener-class>
      org.springframework.web.context.
      ContextLoaderListener
    </listener-class>
  </listener>
. . .
</web-app>

Acegi Security Configuration

Now we'll discuss how to configure Acegi security in the Spring configuration file. As explained earlier, we configured the business logic bean so that method invocations are intercepted by the securityInterceptor bean to perform security checks. Now let's look at how this bean is configured. Shown below is the fragment from the Spring configuration file for the securityInterceptor bean. The securityInterceptor bean is provided by a class from Acegi called the MethodSecurityInterceptor. As the name implies, this class is used to enforce security on method invocations by intercepting the invocations and checking that the invoker is authenticated and authorized.

<beans>
  . . .
  <bean id="securityInterceptor"
    class="org.acegisecurity.intercept.method.
      aopalliance.MethodSecurityInterceptor">
    <property name="authenticationManager">
      <bean class="org.acegisecurity.
        providers.ProviderManager">
        <property name="providers">
          <list>
            <bean
              class="org.acegisecurity.
                providers.anonymous.
                AnonymousAuthenticationProvider">
              <property
                name="key"
                value="changeThis"/>
            </bean>
          </list>
        </property>
      </bean>
    </property>

    <property name="accessDecisionManager">
      <bean
        class="org.acegisecurity.vote.
          UnanimousBased">
        <property name="decisionVoters">
          <list>
            <bean
              class="org.acegisecurity.
                vote.RoleVoter"/>
          </list>
        </property>
      </bean>
    </property>

    <property
      name="objectDefinitionSource">
      <value>
        com.mybank.bizlogic.AccountMgr.
          transferFunds=ROLE_MANAGER
      </value>
    </property>
  </bean>
  . . .
</beans>

We need to configure the securityInterceptor bean with an authenticationManager property to specify what kind of authentication to use. Since our design relies on the Axis handler to perform authentication, we don't need authentication here, so we just configure it with the AnonymousAuthenticationProvider. Plus, the way we create the authentication token in the Axis handler will let Acegi know that it's already been authenticated, so it won't try to authenticate again. We'll explain this in more detail later when we discuss the Axis handler.

Next, we need to configure the bean with an accessDecisionManager property to specify how it will decide whether or not to grant somebody access to invoke a method. Acegi comes with three concrete implementations of an access decision manager: AffirmativeBased, ConsensusBased, and UnanimousBased. Acegi makes an access decision by relying on voters to vote whether or not to grant somebody access to perform a particular action. It tallies up the votes to decide whether or not access should be granted. We've chosen the UnanimousBased access decision manager, which requires that all voters vote to grant access in order for the client to be able to perform the action. You should read the Acegi documentation for a more in-depth explanation of how this works. Next, we have to configure the accessDecisionManager with a list of voters. Here, we'll just use one voter called the RoleVoter. This is a class from Acegi that votes whether or not to grant access based on role-based access control. Again, you should consult the Acegi documentation for more details on how the RoleVoter works.

The final property that needs to be configured is the objectDefinitionSource. This is how we specify what permissions are required to access the various methods on the object that's being secured. Here, we only want to secure the transferFunds() method, and we want to only allow access to managers. We do this by listing the fully qualified class name and method name and the required role for accessing it:

com.mybank.bizlogic.AccountMgr.transferFunds=
  ROLE_MANAGER

Custom Axis Handler

As we described earlier, we need something that will bridge the web service security context with the Acegi security context. This is where our custom Axis handler, AcegiBridgeAuthenticationHandler, comes in. Its source is available in the downloadable sample code. We've kept the implementation very simple to make things easy to explain. The first thing you may notice is that it isn't actually doing any authentication. We just pull the username and password out of the MessageContext and use them as is. A real implementation would, of course, actually try to verify that information. Something else to consider is that a real implementation should extract the WS-Security headers from the SOAP message and process them to get the authentication information, instead of just extracting them from the Axis MessageContext object as we're doing here.

After we have the authentication information, the next step is to make that available to Acegi. This is done by creating an authentication token and setting it in the Acegi security context. Since we're doing username/password authentication, we'll create an instance of the UsernamePasswordAuthenticationToken. Before we do that we need to specify what authorities (i.e., permissions) have been granted to this principal. This is done using the GrantedAuthority interface and a simple implementation of that called the GrantedAuthorityImpl. Since we're using the RoleVoter to make access decisions, the authorities we will be granting are roles. Again, we've simplified the implementation by hardcoding it to grant the principal a role of manager, since that's what's required to invoke the transferFunds() method on our POJO. A real implementation would probably take the username and password and look them up in a database or directory server to find out what roles are actually associated with that principal. Or, in some cases, that information might be available in the WS-Security headers in the form of SAML assertions.

In any case, once this is done, we create an instance of the UsernamePasswordAuthenticationToken, passing in the username, password, and the granted roles. By using this form of the constructor that takes the array of GrantedAuthority, we are actually telling Acegi that this token has already been authenticated so it should not need to authenticate it again. Next, we get the SecurityContext by calling a static method on the SecurityContextHolder and set the authentication token into the SecurityContext. Now the authentication and role information are available downstream for Acegi to use to perform its security checks. Thus, we've effectively bridged the web service security context into the Acegi security context.

There are a couple of additional things to consider. First, Acegi also provides pretty robust authentication capabilities, so instead of having the Axis handler take care of authentication, you can let Acegi do that as well. To do this, create an unauthenticated authentication token by using the constructor that does not take an array of GrantedAuthority. You will also need to make sure that the appropriate authentication provider is configured instead of using the AnonymousAuthenticationProvider. Second, Acegi supports more than just username/password authentication. For example, if you're doing PKI-based authentication, you can use the X509AuthenticationToken instead of the UsernamePasswordAuthenticationToken.

Finally, we need to configure Axis to include this handler in the request-processing path of our service. This is done by adding the following entries to the Axis configuration files deploy.wsdd and server-config.wsdd:

<deployment
  xmlns="http://xml.apache.org/axis/wsdd/"
  xmlns:java="http://xml.apache.org/axis/
  wsdd/providers/java">

  <service name="FundsTransferService"
    provider="java:RPC" style="document"
    use="literal">
    . . .
    <requestFlow>
      <handler type="java:com.mybank.security.
        AcegiBridgeAuthenticationHandler"/>
    </requestFlow>
    . . .
  </service>
</deployment>

Conclusion

Separation of concerns is a key principle to developing Service Oriented Architectures. However, it needs to be applied not only at the architectural level, but at the implementation level as well. In this article, we've demonstrated how to use Axis, Spring, and Acegi to implement a secured web service that adheres to the SOA principle. As you have seen in the sample code, using this approach allowed us to minimize the cross-dependencies in the code that handled each concern of the service. The example we've shown was deliberately kept simple, but it should serve as a basis for you to develop web services with a robust security mechanism combining web services security with application-level security provided by Acegi. As mentioned earlier, a real system will most likely need to develop a handler that can process WS-Security headers and bridge them into the Acegi security context. One way to do this is to take the WSS4J, toolkit from Apache, and extend its Axis handlers to populate the Acegi security context as described in this article. You may have to do some additional work to create an Axis outbound handler that catches the Acegi security exceptions and creates more meaningful SOAP faults to return to the client.

Resources

Tieu Luu is an Associate with Booz Allen Hamilton where he works on architectures and strategies for large enterprise systems.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.