Friday, July 2, 2010

Spring Security: Redirect back to target URL after successful authentication

Most, if not all, users will bookmark their favorite pages in any online application. One would be hard pressed to find folks that bookmark the login pages!

When accessing application URLs directly, if a login page is presented to the web users, they expect to end up at their original destination rather than some application specified home page.

As the Spring Security framework has progressed it has supported this use-case in an evolving manner.

Spring-Security 2.0.x
SavedRequest is used by AbstractProcessingFilter and SavedRequestAwareWrapper to reproduce the request after successful authentication. An instance of this class is stored at the time of an authentication exception by ExceptionTranslationFilter.
  1. If we look at the source code, it is easy to see that for an incoming unauthenticated user, the ExceptionTranslationFilter's sendStartAuthentication(...) method will preserve the original request (+/-)
         SavedRequest savedRequest = new SavedRequest(httpRequest, portResolver);
         httpRequest.getSession().setAttribute(AbstractProcessingFilter.SPRING_SECURITY_SAVED_REQUEST_KEY, savedRequest);

  2. And a successful authentication, AuthenticationProcessingFilter will leverage its parent AbstractProcessingFilter's determineTargetUrl(...) method to determine if it should load the saved target URL (+/-)
         String targetUrl = alwaysUseDefaultTargetUrl
    ? null : targetUrlResolver.determineTargetUrl(getSavedRequest(request), request, SecurityContextHolder.getContext().getAuthentication());

  3. So the only question remaining is how to set the alwaysUseDefaultTargetUrl property? This can be done:
    1. Simply via the http tag (+/-)

      <http ...>
          ...
          <form-login ...
            always-use-default-target='false' />
      </http>

    2. Or via the bean if your configuration happens to be more involved (+/-)
           <bean id="authenticationProcessingFilter" class="org.springframework.security.ui.webapp.AuthenticationProcessingFilter">
           <property name="alwaysUseDefaultTargetUrl" value="false" />
           </bean>

Spring-Security 3.0.x
Spring Security 3.0.x offers this out-of-the-box as well, have a look at this class: SavedRequestAwareAuthenticationSuccessHandler

Here's how it works:
  1. ExceptionTranslationFilter will save the original URL in a RequestCache before the user is sent/redirected to the appropriate location for authentication to happen.
  2. When the user has successful authenticated ... the Filter which extends AbstractAuthenticationProcessingFilter will be able to leverage this parent's already existing successfulAuthentication() method.
  3. The method will further call upon SavedRequestAwareAuthenticationSuccessHandler's onAuthenticationSuccess() to pull out the original URL from the cache where the user should now be redirected for a happy ending.
Here are the odd mistakes that one might make that will cause this well-oiled mechanism to breakdown:
  • You have a Filter that redirects the user somehow before ExceptionTranslationFilter ever gets the chance to store the original URL in the HttpSessionRequestCache.
  • Your Filter, neither extends AbstractAuthenticationProcessingFilter ... nor does it call a AuthenticationSuccessHandler like SavedRequestAwareAuthenticationSuccessHandler for doing this work explicitly.


2 comments:

  1. Please help me with this. I am not redirected to the saved URL instead redirecting to the original URL. Please find the below Stack trace:

    HttpSessionRequestCache:37 - SavedRequest added to Session: SavedRequest[https://localhost:8443/user_spring/viewAllUserSubmissions.jsp]

    SavedRequest:313 - pathInfo: both null (property equals)
    SavedRequest:313 - queryString: both null (property equals)
    SavedRequest:335 - requestURI: arg1=/user_spring/viewAllUserSubmissions.jsp; arg2=/user_spring/login.jsp (property not equals)
    17:54:07,234 DEBUG HttpSessionRequestCache:70 - saved request doesn't match

    Configuration:

    ReplyDelete
  2. Glad you figured it out Satish :)

    ReplyDelete