Separation of Concerns in Web Service Implementations

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:

Web service plumbing to expose the functionality as a service

Business logic for the transferring of funds

Security to ensure only authorized entities can perform the funds transfer

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).

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.