Customizing Spring Security with Legacy Transactions and Authorization

Posted by Tejus Parikh on June 22, 2009

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.
  1. Authenticate the user
  2. Create the Session
  3. Commit the Transaction
  4. Create a PreAuthenticationFilter to load the session and set token in ThreadLocal
  5. Open a Transaction
  6. Let Spring do it’s thing
  7. Commit the transaction and remove token from ThreadLocal
It’s easy in theory, but somewhat difficult to sift through all the documentation and find exactly what you need to do.

Creating a Custom AuthenticationProcessingFilter

You need to extend AuthenticatingProcessingFilter 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 set allowSessionCreation 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 a HandlerInterceptor. 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 the PreAuthenticationFilter so no need to repeat.

Conclusion

It’s a lot of typing, but these are all the entry points you’ll need to make Spring Security work with any legacy cruft. Corrections and comments are always welcome.

Tejus Parikh

I'm a software engineer that writes occasionally about building software, software culture, and tech adjacent hobbies. If you want to get in touch, send me an email at [my_first_name]@tejusparikh.com.