Friday, May 29, 2009

Acegi (Spring Security) and Rampart integration

If you involved to any Spring based web application project, Acegi or Spring Security is not a new thing for you. Acegi is a grate framework that simplifies lot of security headache related to web application. I wrote several blog posts about some of the useful features of Aceigi .

Meanwhile Apache Rampart is a very efficient web service security module for Apache Axis2 that based on Apache WSS4J. I have used Axis2 as a part of Spring J2EE web application rojects, for all those projects I had one common challenge for security integration. Our user/role/access management modules were developed based on Acegi , when a user access to a web page ,Acegi invoke its“AuthenticationManager” to authorize the page access rights based on the provided user credentials . In web service context, same user send a web service request along with same credentials (as an example user name name /password) as a security headers and it is required to invoke “AuthenticationManager” through Rampart to authorize the access rights.

“CallbackHandler” is the underline Rampart mechanism to inject our security logic in to the Rampart module. But one of the critical problem here is there is no way to access to Axis2 MessageContext within the CallbackHandler and hence not possible to access either Spring security context or Spring AppicationContext.

Recently I saw number of post on the mailing list pointing out same requirement without having proper answer. In those days with our tide schedule I end up with a simple but little ugly solution for this.
I used same database table (that contains user security information) for both Acegi and Rampart, in Acegi I injected my user information using “UserDetailService” and for Rampart I wrote small JDBC based DAO service and injected into “CallbackHandler”. But later I could come up with a smart approach for this, in this approach the same acegi AuthenticationManager will used by the Rampart also without reloading. Following steps will help out to any one facing to similar problems.

Step -1
Create a class called “Axis2AwareContextLoaderListener” that extend from Spring ContextLoaderListener . Then in the contextInitialized method set Spring ApplicationContext in to Axis2 ApplicationContextHolder as follows.


public class Axis2AwareContextLoaderListener
extends ContextLoaderListener {

/** The holder. */
ApplicationContextHolder holder;

public void contextInitialized(ServletContextEvent event) {
super.contextInitialized(event);
ApplicationContext appCtx = (ApplicationContext) event
.getServletContext()
.getAttribute(
WebApplicationContext.
ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
holder = new ApplicationContextHolder();
holder.setApplicationContext(appCtx);

}

public void contextDestroyed(ServletContextEvent event) {
super.contextDestroyed(event);
holder = null;
}

}

Step -2
Now in the CallbackHandler implmentaion class we can access to Spring ApllicationContext as follows through the getContext() method of ApplicationContextHolder.

ApplicationContext secCtx = ApplicationContextHolder.getContext();
AuthenticationManager am = (AuthenticationManager) secCtx
.getBean(AUTHENTICATION_MANAGER_BEAN_NAME);


Step -3
Create an instance of Acegi Authentication object with credentials values of Rampart Callbacks object.


public class ServerPWCBHandler
implements CallbackHandler {

/** The Constant AUTHENTICATIONMANAGER_BEAN_NAME. */
public static final String
AUTHENTICATIONMANAGER_BEAN_NAME =
"authenticationManager";

public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {

ApplicationContext secCtx = ApplicationContextHolder.getContext();
AuthenticationManager am = (AuthenticationManager) secCtx
.getBean(AUTHENTICATIONMANAGER_BEAN_NAME);

for (int i = 0; i < callbacks.length; i++) {

// When the server side need to authenticate the user
WSPasswordCallback pwcb = (WSPasswordCallback) callbacks[i];

if (pwcb.getUsage() == WSPasswordCallback.
USERNAME_TOKEN_UNKNOWN) {
try {
Authentication aut =
new UsernamePasswordAuthenticationToken(
pwcb.getIdentifier(), pwcb.getPassword());
am.authenticate(aut);

} catch (Exception e) {
throw new UnsupportedCallbackException(callbacks[i], e
.getMessage());
}

}

}
}

}



If you want to look at any sample you can download it from here, this sample is a combination of Spring/Acegi/Tapestry5 web application and a Axis2 /Rampart based web service, both web application and web service use values of users.properties file for user authentication.

No comments: