Saturday, 7 November 2009

Create a default access rule for URLs using Spring Security

Using Spring Security, you can specify access rules for URLs in a declarative manner in a central configuration file. However, it's easy to forget a controller or two, which could have tragic, or at least embarassing, consequences!

One way to avoid that is to create a catch-all rule that will ensure that all URLs are secured. This will be fired if you access a forgotten URL, both prompting you to fix but also preventing anyone exploiting the mistake.

The key to it is realising that the first matching rule from the top is applied to each URL - therefore, defining a catch-all at the bottom will ensure that any unmatched URL is matched by the catch all. The pattern matching is based on Ant patterns, so /** will do the trick.

This is demonsrated below.



  
     
  
    
    
    
    
    
    
   
    
    
   
    
   
    
  


Note that the login and logout URLs have been set to ROLE_ANONYMOUS. Otherwise, attempts to access them will result in an infinite loop as the catch all will fire. ROLE_ANONYMOUS is the default role given to all unauthenticated requests by Spring Security - if you have overridden it, use the overridden name instead.

Using Spring Security with Tuckey URLRewriteFilter

If you are developing a servlets, JSF or Spring MVC application, it’s very likely you will want to implement declarative security to restrict access to functions based on URLs. It's also likely that you want to provide intuitive, SEO friendly and RESTFul URLs for your app.

Luckily, both of these requirements can be met by existing, high-quality open source frameworks in a way that is not intrusive to your application code. I’m referring to the Spring Security framework (what used to be Acegi Security) and the Tuckey URLRewriteFilter.

I won’t go in to the details of setting up both of these frameworks, as these are well covered in other excellent blogs:

http://sziebert.net/posts/restful-urls-with-spring-mvc-and-urlrewritefilter/
http://www.mularien.com/blog/2008/07/07/5-minute-guide-to-spring-security/

However, as I discovered after some false starts, getting both of these components to work together needs the correct configuration, which might not be apparent unless you really understand how Servlet Filters work.

Both frameworks rely on servlet filters to apply rules to requests – the Spring Security framework’s filter chain applies access decision rules to incoming requests and the URLRewriteFilter intercepts requests and forwards them to the re-written URL. The order in which these things occur is obviously quite important.

For example, consider this simplified Spring Security configuration (which goes in your application context).




This rule restricts the URL /clientlist.htm (which maps to a Spring MVC controller but could be a servlet or JSP) to users who are in the ROLE_MANAGER role. Note that the pattern matching is based on Ant pattern matching, and this is described in the Spring Security documentation.

However, we want this to be accessed on the more friendly URL of /clients. To do that we use a URL rewriting rule in the URLRewriter filters configuration file:


^/clients$
  /clientlist.htm?status=1

Of course, we would also need a corresponding outbound rule:


  /clientlist.htm\?status=1$
/clients

However, that’s not really important for the purposes of security. The important thing to note is that the URL Rewriter will take a request for /clients and forward it to /clientlist.htm?status=1. At the same time, the Spring Security filter applies access decisions to requests for /clientlist.htm.

Clearly, the order that the filters process the request in is important. For this to work, we would want the URL rewriting to happen first (translating /clients to /clientlist.htm) before the security rules are applied. Otherwise, Spring Security would need to be configured to match the re-written URLs rather than the “real” URLs. While this is technically feasible it might cause maintenance difficulties. Imagine how much head-scratching would be involved for a maintainer trying to understand why the Spring Security config doesn’t match up with the expected URLs for the Spring Controller definitions!

We all know (because we memorised the Servlets specification) that Filters are applied in the order that they are defined in the web.xml. So easy, we just put the URL rewriter in front of the Spring Security filter in the web.xml! Job’s a good ‘un, now what’s for lunch?

Not so fast... Just defining the filters in the right order is not enough on its own, especially if using the standard configuration examples provided in the documentation for both products.

The symptoms of an incorrect configuration would be that URL rewriting works but the Spring Security filter does not process the request, resulting in either an error (if your code expects an authenticated user) or in security not being applied.

That is because the URL rewriter forwards the request to the rewritten URL. Having memorised the servlet spec, we know that filters do not automatically process forwarded requests. By default, they only apply to direct requests. Luckily, this can be easily changed using the <dispatcher /> element in the filter-mapping configuration.

A working configuration of Tuckey URLRewriteFilter and the Spring Security filter including the additional <dispatcher>FORWARD</dispatcher> configuration is included below:




    UrlRewriteFilter
    
       org.tuckey.web.filters.urlrewrite.UrlRewriteFilter
    
    
       logLevel
       WARN
    
    
       confReloadCheckInterval
       
    




   UrlRewriteFilter
   /*

 


    springSecurityFilterChain
    org.springframework.web.filter.DelegatingFilterProxy  
  



    springSecurityFilterChain
    /*
    REQUEST
    FORWARD
    INCLUDE
    ERROR

Adding the <dispatcher /> elements within the filter-mapping element for the Spring Security filter will cause it to process the forwarded request. It means you can configure security based on the “real” URLs rather than the rewritten URLs. Note you should also make the filter apply to direct requests, otherwise you won't be able to access Spring Security URLs like j_spring_security_check (unless you use the URL rewriter to create a forward to this URL, which seems unnecessary).

As a final remark; note that any other filters that you want to apply to requests will need to be configured as above as well. For example, the Spring Hibernate “open session in view” filter that is needed for lazy initialisation and loading in the web tier:



    hibernateFilter
    
org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
   
   
       sessionFactoryBeanName
       sessionFactory
    




    hibernateFilter
    /*
    REQUEST
    FORWARD
    INCLUDE
    ERROR

Hope this is useful to someone.