A few months ago at work I got stuck with a rather daunting assignment: to make Spring Security work alongside our legacy security model. The rationale was sound. We have a legacy UI and we want a smooth transition to the new one. Which means that as much of their information, including their credentials need to carry over. Furthermore, our application runs load-balanced in the production environment and we can’t make use of sticky sessions. Which means that the solution needs to integrate with our database-backed sessions. If that was not complicated enough, there was also a lot of hidden authorization code that relied on specific properties being set in ThreadLocal. After a few months of trial and error, I think I finally have a solution that both works and doesn’t lock the database. There are quite a few steps and the process is somewhat lengthy. For that reason, the rest of this tutorial is under the fold.
Important Things to Understand Before You Start
Spring Security works as a servlet filter and will execute before the interceptors or controllers. This is crucial if you are relying on one of those mechanisms for your transaction management. The Spring Security filter chain can be configured with a arbitrary number of filters, but subsequent filters will only execute if the previous filter calls:
filterChain.doFilter(request, response);
One filter that does not call this method is the DefaultAuthenticationFilter.
When the DefaultAuthenticationFilter
handles the authentication, it terminates execution of the filter chain and replays the original request, separate from the authentication request. This second request could occur on an entirely different thread, or even a different server in a load-balanced environment. This has implications for both transaction management and thread-local based legacy authorization.
I’ve drawn up a diagram to try and explain what’s happening:
[flickr]3651548939[/flickr]
What You Need to Do
Once that’s understood, it’s pretty clear what needs to happen.- Authenticate the user
- Create the Session
- Commit the Transaction
- Create a PreAuthenticationFilter to load the session and set token in
ThreadLocal
- Open a Transaction
- Let Spring do it’s thing
- Commit the transaction and remove token from ThreadLocal
Creating a Custom AuthenticationProcessingFilter
You need to extendAuthenticatingProcessingFilter
in order to configure your own backing sessions and perform your own cleanup. Mine looks like this:
package net.vijedi.spring;
public class CustomAuthenticationProcessingFilter extends
AuthenticationProcessingFilter {
@Override
protected void onSuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, Authentication authResult) throws IOException {
createSession(request, response, authResult);
commit();
super.onSuccessfulAuthentication(request, response, authResult);
}
@Override
protected void onUnsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, AuthenticationException failed) throws IOException {
commit();
super.onUnsuccessfulAuthentication(request, response, failed);
}
}
It’s important to implement custom logic in both over-ridden methods since both will short circuit the rest of the stack.
In order to use your own filter, you must disable auto configuration and add the following lines to the spring security configuration file:
The line
makes the authenticationManager available to inject into other beans. The authenticationProcessingFilterEntryPoint
configures what page is shown when the user is asked to log in. This is configured for if you were using Spring’s auto-configuration.
Creating the PreAuthenticationFilter to Re-Authenticate
Since you’ve setallowSessionCreation
to false
you’ll need to re-authenticate with every request. My scheme uses a cookie set in the createSession
method mentioned above.
Whatever your scheme is, you’ll need to create another filter and set it to execute before the AuthenticationProcessingFilter
.
package net.vijedi.spring;
public class PreAuthenticationFilter extends AbstractPreAuthenticatedProcessingFilter {
/**
* Try to authenticate a pre-authenticated user with Spring Security if the user has not yet been authenticated.
*/
@Override
public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
try {
loadSession(request);
if (SecurityContextHolder.getContext().getAuthentication() == null) {
doAuthenticate(request, response);
}
} catch (SecurityException se) {
LOG.warn("The cookie is valid, but there is no corresponding session in the database");
}
filterChain.doFilter(request, response);
}
@Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
return SessionUtil.getUsername();
}
@Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return SessionUtil.getPassword();
}
}
First, I load the session from the cookie, which puts the credentials in thread local. I then use those credentials to re-authenticate with Spring.
Of course, this requires some corresponding xml:
Clean Up After Yourself
Now that I’ve stuck something into ThreadLocal, I need to make sure it gets removed at the end of request processing. This is where I need aHandlerInterceptor
. I’m going to implement the afterCompletion
method, since I want this to be the last thing run before the request is finished processing.
package net.vijedi.spring;
public class SecurityInterceptor implements HandlerInterceptor {
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) throws Exception {
SessionUtil.clear();
commit();
}
// Other methods removed for brevity
}
This removes the authentication from ThreadLocal as well as committing any open transaction. This bit of xml needs to go in the *-servlet.xml
file and not in the security file:
Logging Out
Almost done, but you also need a custom filter for logging out. This follows the same pattern as thePreAuthenticationFilter
so no need to repeat.