Sunday 21 October 2012

Spring Security form-login behind the scene

In Spring in Action 3rd Edition, chapter 9, Securing Spring, page 228, here is a summary of the main points on this page.

The filter is defined in web.xml as:

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
  
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

The name of the filter springSecurityFilterChain is significant, meaning that you cannot give it an arbitrary name or the Spring security framework won't be able to find this filter.

The DelegatingFilterProxy doesn't do much and delegate the work to a special filter known as FilterChainProxy.

The FilterChainProxy is a single filter that chains together multiple additional filters. These filters, along with the FilterChainProxy, are created by Spring based on the security configuration. We will never need to explicitly declare the FilterChainProxy bean, so we don't need to know the details.

Ok, this is the gist of the page. As much as it says we don't need to know the details, I still have a few questions in mind.

  • How does FilterChainProxy chain together multiple other filters?
  • Which filter checks whether the provided username and password match the true credential?
  • There is an implicit object known as 'currentUser' in Spring Webflow. At what scope (request, session, flow) is this object stored?


Part 1.  How does FilterChainProxy chain together multiple other filters?


I am going to use the booking-face Spring Webflow sample project.

Let's start deployment and put a break point at DelegatingFilterProxy.java @Line 226. 

this.delegate = initDelegate(wac);

Step into it. DelegatingFilterProxy.java @Line 326. 

Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);

After this line is executed, we see delegate is an instance of FilterChainProxy, and it contains lots of filters already.

FilterChainProxy[ UrlMatcher = org.springframework.security.web.util.AntUrlPathMatcher[requiresLowerCase='true']; Filter Chains: {/**=[org.springframework.security.web.context.SecurityContextPersistenceFilter@10896d0, org.springframework.security.web.authentication.logout.LogoutFilter@e542a1, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@179763c, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@1416e4f, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@b11bbf, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@194aa64, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@13e985a, org.springframework.security.web.session.SessionManagementFilter@8475c4, org.springframework.security.web.access.ExceptionTranslationFilter@4fd3a5, 
org.springframework.security.web.access.intercept.FilterSecurityInterceptor@d03044]}]

It is easily seen that this FilterChainProxy bean has long been created and stored in the web application context. The ContextLoaderListener was in play and if interested, please refer to my article What does ContextLoaderListener do in Spring?

Now we put a break point at DelegatingFilterProxy.java @Line 259 and launch the application (by go to the url http://localhost:8080/booking-faces/spring/main).

invokeDelegate(delegateToUse, request, response, filterChain);

Step into it. DelegatingFilterProxy.java @Line 346

delegate.doFilter(request, response, filterChain);

Step into it. FilterChainProxy.java @134-149

FilterInvocation fi = new FilterInvocation(request, response, chain);
List<Filter> filters = getFilters(fi.getRequestUrl());

if (filters == null || filters.size() == 0) {
    if (logger.isDebugEnabled()) {
        logger.debug(fi.getRequestUrl() +
        filters == null ? " has no matching filters" : " has an empty filter list");
    }

    chain.doFilter(request, response);

    return;
}

VirtualFilterChain virtualFilterChain = new VirtualFilterChain(fi, filters);
virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse());

This is the central part. It differs from an everyday Filter in that for an everyday filter, after you do some processing, you invoke chain.doFilter() to give other filters chance to do their work. A typical example is this CharacterEncodingFilter

protected void doFilterInternal(
        HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {

    if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) {
        request.setCharacterEncoding(this.encoding);
        if (this.forceEncoding) {
            response.setCharacterEncoding(this.encoding);
        }
    }
    filterChain.doFilter(request, response);
}

The FilterChainProxy also has this line @Line 143, am I too blind to notice that? Actually this line will not be executed. To be precisely, not until all the spring created filters have done their job.

We can press F6 to verify. Line 137-146 are skipped, and we are at Line 148. We are going to step into Line 149. But before we do, I want to take note of the chain object in the method argument list. It is org.apache.catalina.core.ApplicationFilterChain@9a731a

virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse());

FilterChainProxy.java @Line 355

nextFilter.doFilter(request, response, this);

Step into it. Now we are at the very first of the so called "additional filters". SecurityContextPersistenceFilter.java @Line 50.

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)

Inspect the chain object, it is org.springframework.security.web.FilterChainProxy$VirtualFilterChain@1d418d4. The chain object is no longer the one created by the Application server (Glassfish, in this case). It now becomes the VirtualFilterChain created by Spring.

This is how, even though not defined in web.xml, these "additional filters" got chained together to do their job. Had we defined them in web.xml, we wouldn't have needed VirtualFilterChain. But the web.xml would have exploded and that would be the last thing we want to see. (Configuration is really pain in the ass)


Part 2.  Which filter checks whether the provided username and password match the true credential?


It is the UsernamePasswordAuthenticationFilter.

Let's put a break point @Line 97.

return this.getAuthenticationManager().authenticate(authRequest);

Just press F7 to jump out of this method and we are at AbstractAuthenticationProcessingFilter.java @Line 205

authResult = attemptAuthentication(request, response);

Step down till @Line 219

successfulAuthentication(request, response, authResult);

Step into it. AbstractAuthenticationProcessingFilter.java @Line 293

SecurityContextHolder.getContext().setAuthentication(authResult);

Put the authentication information into security context. The security context is saved in a thread local object.

AbstractAuthenticationProcessingFilter.java @Line 302

successHandler.onAuthenticationSuccess(request, response, authResult);

Step into it...eventually it hits SaveContextOnUpdateOrErrorResponseWrapper.java @Line 73

doSaveContext();

This line saves the security context into session.

Here is some  pseudocode to describe the login process.

String username = getUsername(request);
String password = getPassword(request);
boolean loginSuccessful = authenticationManger.authenticate(username, password);
if (loginSuccessful){
     SecurityContext sc = new SecuirtyContext(username);
     SecurityContextHolder.set(sc);
     session.setAttribute("SecurityContextKey", sc);
     redirectToLoginSuccessPage();
}else{
     redirectToLoginFailurePage();
}

Since we already save the security context into the session,  why put it into SecurityContextHolder object again?

Because SecurityContextHolder put the security context into a thread local object. Any plain java class can easily access the security context by calling the static method SecurityContextHolder.get(); However, it is not so easy to grab a session for a plain java class.


Part 3.  There is an implicit object known as 'currentUser'. At what scope (request, session, flow) is this object stored?



The implicit object 'currentUser' can be used at Spring Expression Language in web flow xml file or JSP/JSF page.

e.g. main-flow.xml

<evaluate expression="bookingService.findBookings(currentUser?.name)" result="viewScope.bookings" result-type="dataModel" />

e.g. enterSearchCriteria.xhtml

<p:panel id="bookings" header="Your Hotel Bookings" rendered="#{currentUser!=null}" ...>


We can have a look at ImplicitFlowVariableELResolver.java and FlowVariablePropertyAccessor.java. Both put 'currentUser' as a key into a static map, and the value corresponding to 'currentUser' is an object that is connected to the request context. The request context holds the external context, which holds the security context.

So the attribute 'currentUser' is never explicitly put into any scope.

When the EL resolver sees 'currentUser', it will find it in the security context attached to the request context. There is an easy way to obtain the request context by calling RequestContextHolder.getRequestContext(). The RequestContextHolder, working similarly as the way SecurityContextHolder does, put the request context into a thread local object.

We learn from part 2 and part 3 that there are two ways to obtain the current user programmatically. Thanks to thread local, they are both very straightforward:

RequestContextHolder.getRequestContext().getExternalContext().getCurrentUser();

SecurityContextHolder.getContext().getAuthentication().getPrincipal();