Search This Blog

Sunday, February 12, 2012

Spring Security - Stateless Cookie Based Authentication with Java Config

It has been security time for me recently at work, single sign on and the likes. While at it, I stumbled upon my favorite framework Spring and its offering Spring Security. In the words of the creators of the framework, "Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications".  Spring Security has been around since sometime now but I have not had a chance to use it. This is my first foray into the framework and my BLOG is targeted at accomplishing the following objectives:

1. Enhance the Example Project I created to demonstrate Spring Java Config with Authentication and Authorization powered by Spring Security.
2. Demonstrate an embedded LDAP server in the project as the source of user authentication and user roles.
3. Ensure that the application is stateless thus being able to scale out really easily. Use a  Cookie instead of HttpSession to store the user's authenticated state.
4. Have the application use Form Based Authentication.
5. Use Java Config for Spring MVC and Spring Security

The Example Flight project was augmented with the following ROLE based authorization:

1. Standard User - A user who can view Airports, Search Flights and see reservations
2. Agent - An agent who can perform all the operations a user can with the additional ability to  make reservations on flights
3. Administrator - An administrator who can  do everything a standard user can do with the additional ability to create new Airports for the Flight application. They however cannot make a reservation, i.e., no Agent privilege.

From an LDAP perspective, the following groups can be considered to match the above roles: USER, AGENT and ADMIN.

ApacheDS is one example of an open source LDAP server that can be run in embedded mode. I started looking into how to do the same only to find out that the Spring Security project provides a very easy way to the same. If using Spring Security, the standard way to have an embedded server in your application for demonstration or testing purposes is to simply have the following line defined in your spring beans.xml. krams BLOG on using embedded LDAP with Spring MVC was a major help in gettting the code functional:
 <--ldiff file to import -->
 <ldap-server root="o=welflex" ldif="classpath:flightcontrol.ldiff" />

The above claim seems exotic but the presence of the above line and the magic of  Springs Extensible XML Authoring has an embedded LDAP going with minimal developer effort. The Java Config equivalent of the same is shown below where the container is programatically created:
@Configuration
public class SecurityConfig {
  /**
   * This bean starts an embedded LDAP Server. Note that start
   * is not called on the server as the same is done as part of the bean life cycle's
   * afterPropertySet() method.
   *
   * @return The Embedded Ldap Server 
   * @throws Exception
   */
  @Bean(name = "ldap-server")
  public ApacheDSContainer getLdapServer() throws Exception {
    ApacheDSContainer container = new ApacheDSContainer("o=welflex",
        "classpath:flightcontrol.ldiff");
    container.setPort(EMBEDDED_LDAP_SERVER_PORT);
    return container;
  }
...
}
With the above Java Config, the embedded LDAP server is primed with the contents from the flightcontrol.ldiff file.

For the Stateless requirement, the flight control application will rely on a Cookie on the client browser to detect the user that is logged in rather than use the HTTP Session. Spring Security tends to gravitate toward using the HTTP Session. However as of Spring 3.1, they introduced a configuration of "stateless".  The setting of stateless meant that a HTTP session would not be created by Spring Security. The same however does not translate into a Cookie based authentication mechanism being introduced. Using name spaces, one would bootstrap spring security for the application as shown below:
  <http auto-config="true" create-session="stateless">
      <-- Allow anon access -->
      <intercept-url pattern="/login*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
      <intercept-url pattern="/logoutSuccess*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
      <intercept-url pattern="/scripts*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
      <--Enforce user role -->
      <intercept-url pattern="/*" access="ROLE_USER" />

      <-- Form login, error, logout definition -- >
      <form-login login-page="/login.html"
    always-use-default-target="true" default-target-url="/home.html"
     authentication-failure-url="/login.html?login_error=1" />
          logout logout-url="/logout" logout-success-url="/logoutSuccess.html" />
  </http>
In order to understand how the above Spring Security namespace is translated to Java Beans, take a look at the BLOG by Luke Taylor on the same. The above will work fine if one is using Basic or Digest authentication to secure resources but does not work with form based authentication and a stateless environment. If you wish to use Basic or Digest based authentication, then take a look at baeldung's BLOG on the same.

As the requirement of the flight application is to use form based login via Cookie Support, a filter is  created that is a substitute for the SessionManagementFilter:
public class CookieAuthenticationFilter extends GenericFilterBean {

  public CookieAuthenticationFilter(LdapUserDetailsService userDetailService,
      CookieService cookieService) {...}

  @Override
  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
    ServletException {
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;

    SecurityContext contextBeforeChainExecution = loadSecurityContext(request);
    
    // Set the Security Context for this thread
    try {
      SecurityContextHolder.setContext(contextBeforeChainExecution);
      chain.doFilter(request, response);
    }
    finally {
      // Free the thread of the context
      SecurityContextHolder.clearContext();
    }
  }

  private SecurityContext loadSecurityContext(HttpServletRequest request) {
    final String userName = cookieService.extractUserName(request.getCookies());

    return userName != null
        ? new CookieSecurityContext(userDetailService.loadUserByUsername(userName))
        : SecurityContextHolder.createEmptyContext();
  }
}
In the above code, the loadSecurityContext method is responsible for loading user information from the authentication provider. The method will attempt to obtain the authenticated principal from the custom user cookie set and if present, create a Spring SecurityContext and set the same in the SecurityContextHolder. Setting of the Context will ensure that no further authentication is performed and the requested resource will be served up. If the User security cookie were not present, then an empty context is provided so that filters further downstream can re-direct the request to a login page.

So where does the User Cookie get set? After a successful authentication, an AuthenticationSuccessHandler is invoked by Spring Security's UsernamePasswordAuthenticationFilter and the former is where the cookie gets set:
public class AuthSuccessHandler implements AuthenticationSuccessHandler {
  ... 
  @Override
  public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
    Authentication authentication) throws IOException, ServletException {
    SecurityContext context = SecurityContextHolder.getContext();
    Object principalObj = context.getAuthentication().getPrincipal();
    String principal = ((LdapUserDetails) principalObj).getUsername();
    
    // Create the User Cookie        
    response.addCookie(cookieService.createCookie(principal));
    response.sendRedirect("/home.html");
  }
}
The UserNamePasswordAuthenticationFilter is notified of the custom AuthenticationSuccessHandler as shown below:
@Configuration
public class SecurityConfig {
  ....
  private UsernamePasswordAuthenticationFilter getUserNamePasswordAuthenticationFilter() {
    UsernamePasswordAuthenticationFilter filter = new UsernamePasswordAuthenticationFilter();

    filter.setAllowSessionCreation(false);
    filter.setAuthenticationManager(getAuthenticationManager());
    // Set the Auth Success handler
    filter.setAuthenticationSuccessHandler(new AuthSuccessHandler(getCookieService()));
    filter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/login.html?login_error=1"));
    
    filter.setFilterProcessesUrl("/j_spring_security_check");

    return filter;
  }
}
The final chain of Filters is declared as:
@Configuration
public class SecurityConfig {
  ....
  @Bean(name = "springSecurityFilterChain")
  public FilterChainProxy getFilterChainProxy() {
    SecurityFilterChain chain = new SecurityFilterChain() {

      @Override
      public boolean matches(HttpServletRequest request) {
        // All goes through here
        return true;
      }

      @Override
      public List<Filter> getFilters() {
        List<Filter> filters = new ArrayList<Filter>();

        filters.add(getCookieAuthenticationFilter());
        filters.add(getLogoutFilter());
        filters.add(getUserNamePasswordAuthenticationFilter());
        filters.add(getSecurityContextHolderAwareRequestFilter());
        filters.add(getAnonymousAuthenticationFilter());
        filters.add(getExceptionTranslationFilter());
        filters.add(getFilterSecurityInterceptor());

        return filters;
      }
    };
    
    return new FilterChainProxy(chain);
  }
}
With the above configurations, the application has all the necessary ingredients to provide Cookie based authentication and Role based authorization. The following HTML snippets demonstrate how Spring Security's taglibs are used to honor the roles on the view tier of the architecture:
<-- Airports page -- >
<sec:authorize ifAllGranted="ROLE_ADMIN">
   <-- Display the form to add an airport -- >
</sec:authorize>

<-- Flight Search page. Display column to book flight only for AGENTs -->

<display:table name="flightSearchResult.flights" class="table"
 requestURI="" id="flight" export="true" pagesize="10">
         .....
 <sec:authorize ifAllGranted="ROLE_AGENT">
  <display:column title="" sortable="false" href="/bookFlight.html"
   paramId="id" paramProperty="id" titleKey="flightId">
           Book
        </display:column>
    </sec:authorize>
</display:table>
Individual methods of the Service classes can also be secured for authorization using Spring Security via annotations. The examples does not get into the same.

Conclusion

The example provided uses Java Config in its entirety and can give an entrant into Spring Security an idea of how the request chain is set up simply by following the Java Config. The beauty of the Spring Security framework lies in the fact that authentication and authorization details are set up within the filter chain and clearly remove boiler plate from a developer who is working of the Controller tier. Spring's Security does not have to stop at the view tier and can be used to authorize access to to Service tier methods as well.

The example obtains the user roles from the embedded LDAP server on every request. The can be performance issue. Consider not prematurely optimizing as LDAP look ups are supposed to be rapid. If you do face a problem with performance, consider a backing cache. Also note that the user cookie created is not secure and can easily be spoofed. In an actual implementation one would use some way to identify against spoofed cookies. 

Download the full source code as a maven example from here and once extracted, execute a "mvn jetty:run" to start the same. All the security configuration is set up in the package com.welflex.web.security. You will see an embedded LDAP server start and the corresponding schema imported into the server. Access the web application at http://localhost:8080.

When prompted to login use one of the following user name/password combinations to see the different roles and corresponding access control in action:

1. Admin User - sadmin/pass
2. Agent User - sagent/pass
3. Standard User - suser/pass

Investigate your browsers cookies to see that a USER cookie has been set by the application and that there is no sign of JSESSIONID cookie.

I hope this is of help to someone trying to integrate Spring Security into their application and wants to use form based login with Cookies to remember the user. There have been some Stack Overflow and Spring Security Forum postings asking about the same.

There are still areas to explore with the Spring Security domain like Spring Security Oauth, Spring Security SAML (empty page)Spring Security CAS.

12 comments:

Anonymous said...

Thanks for this post, I recently started using java based configs for my spring mvc projects and this was immensely helpful as there is absolutely no documentation for java based spring security configuration. I did run into a few issues / dilemmas that the next person that looks here might find useful:
1. I decided to extend springs TokenBasedRememberMeServices instead of starting a cookie management system from scratch (stateless was not a requirement for me, but I am sure this filter works both ways).
2. The ordering of filters here might not be correct. The cookie authentication filter (whichever you use) should be placed after your main authentication filter as mentioned in the docs http://static.springsource.org/spring-security/site/docs/3.0.x/reference/security-filter-chain.html
3. Without the SecurityContextPersistenceFilter, security chain will keep dropping your session unless you persist it in a cookie. Obviously on a stateless setup this is not an issue, but if anyone is trying to maintain state make sure you add that filter at the very top.

Sanjay Acharya said...

Glad that it helped. Great that you are using the TokenBasedRememberMeServices, that works well for this case. Ordering of filters is not a problem here as the CookieAuthenticationFilter is poorly named, maybe it had better be called CookieSecurityContextFilter as it is responsible for loading a context at the start of the chain. One thing to note with my example is that the cookie is not refreshed. Something that would need to be done on a call. Also the example does not demonstrate Java Config for method annotation. Tried to emulate Global security but did not succeed.

Sebastian said...

I'm a bit surprised. I deployed your application in JBoss AS 7.1.1. As it doesn't get deployed in root context, /home can't be found. But this might be just a minor misconfiguration of the request mapping.

But while access the login page, I see that a JSESSIONID cookie is set in the request header. I checked it with Firefox Findbugs. Why is this JSESSIONID created? I expected that it shouldn't exist, because otherwise several app server nodes would need to share the sessions making the app stateful?

Sanjay Acharya said...

The example code should not depend or create a JSESSIONID cookie. When you run under Jetty, you will notice that no JSESSIONID cookie is created. A JSESSIONID will be created if any code calls request.getSession(). In the JSP's of the example, all of them are set to "NOT" create a session as well.

If I were to guess, this is happening at the JBoss container level or some other JSP in the container. That said, the example is still stateless as it does not use the JSESSIONID for any purpose. If you had a load balancer, you could balance across different nodes without a problem as the custom cookie created by the application (AUTHCOOKIE) is only one that is used for authentication/authorization.

Sanjay Acharya said...

Also you might want to see http://stackoverflow.com/questions/595872/under-what-conditions-is-a-jsessionid-created

Regarding the context. It is possible that the code is not using ${ctx} on all links leading the problem. Easily fixed but this is an example :-)

Sebastian said...

I think you are right. The JSESSIONID is most probably created by the container, e.g. when opening the root context, which provides some kind of welcome page.

Anonymous said...

Hi Sanjay,

Thank you for great article!
I'm new in spring that's why I'm asking how to make similar configuration but using xml configs only? Is it possible?

Sanjay Acharya said...

You should be able to create an equivalent configuration by using XML. In fact, that appears to be the default way of doing the same.

MJ said...

Very thorough and helpful!

Pedro Pedruzzi said...

Very helpful article.
However, the example is missing a fundamental component abstracted by cookieService: generation and verification of the authentication cookie value.
Message Authentication Code comes to mind. Encoding a timestamp would also be appropriated to add a expiration policy.
Have you looked into that? What are you using in practice?

Anonymous said...

Thanks for the detailed explanation. I am new bee out here, can you please post the SpringSecurityConfig.java with inmemorydaoimpl

nityanandasahoo said...

i m getting error

java.lang.UnsupportedOperationException: Should not be called by the code path at com.welflex.web.security.CookieSecurityContext.setAuthentication(CookieSecurityContext.java:26) at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.successfulAuthentication(AbstractAuthenticationProcessingFilter.java:314) at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.successfulAuthentication(AbstractAuthenticationProcessingFilter.java:288) at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:213) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:323) at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:105) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:323) at com.welflex.web.security.CookieAuthenticationFilter.doFilter(CookieAuthenticationFilter.java:49) at