Saturday 7 November 2009

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.

3 comments:

  1. Hello.
    I tried to implement this solution.
    But I've got a little problem.
    Spring authentification manager redirects me to /app/admin instead of /admin

    I user urlrewriter which rewrites me from /* to /app/*

    Maybe you know how to fix it ?

    ReplyDelete
  2. I am getting the same problem and wondering if anyone solved it. My guy redirects instead of forwards with the same config.

    ReplyDelete
  3. I get into infinite loop by doing this...below is my applicationContext-security.xml, any idea?

    intercept-url pattern="/login.jsp*" filters = "none"
    intercept-url pattern="/**" access = "isAuthenticated()"

    and my web.xml:
    filter-mapping
    filter-name> UrlRewriteFilter /*Spring OpenEntityManagerInViewFilter/*

    filter-mapping>
    filter-name>
    springSecurityFilterChain
    url-pattern>/*REQUESTFORWARDINCLUDEERROR
    rule>
    from>/app/**
    to last="true">/app/$1
    rule>
    from>/**/app/$1
    outbound-rule>
    from>/app/**/$1
    /outbound-rule>
    /urlrewrite>

    ReplyDelete