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.
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).
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
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,
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 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 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 finally processes that request and returns the results, which are ultimately propagated back to the client program.
Figure 2. Sequence diagram showing processing flow through the service implementation (click for full-size image).
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,
<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>
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
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>
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:
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
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:
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
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>
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.
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.