Tuesday, April 12, 2011

Fine grained authorization with Spring Security 3

For a project I'm currently working on the authorization requuirements are such that we need method level authorization.
The projects will create a REST service whoes functionality I will not describe here, but it is based on Spring MVC, requires authentication based on Kerberos and user ID/password and fine grained authorization based on roles. The roles are taken from the Active Directory.

Since Spring is already used and because we need two distinct manners for authentication, spring security seems to be the best choice for this.
Using spring security, the authorization checks can be configured using annotations on the MVC controller using @PreAuthorize("hasRole('ROLE_USER')") and the like.

Starting with spring security works like a breeze, until you try to step outside of the boundaries.
Setting up simple authentication based on hard coded user ID's with hard coded password and hard coded roles goes like this:
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:sec="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
          http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd
          http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
    ">

    <sec:http auto-config="true">
        <sec:intercept-url pattern="/*" access="ROLE_USER" />
    </sec:http>

    <sec:authentication-manager alias="authenticationManager">
        <sec:authentication-provider>
                <sec:user-service>
                    <sec:user authorities="ROLE_USER"
                        name="jim" password="morisson"/>
                    <sec:user authorities="ROLE_USER,ROLE_OPERATOR"
                        name="stevie" password="vaughan"/>
                    <sec:user authorities="ROLE_USER,ROLE_OPERATOR,ROLE_DEPLOYMENT_ENGINEER"
                        name="brian" password="jones"/>
                </sec:user-service>
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>

According to the documentation, all you need to do to enable the @PreAuthorize annotation is adding <sec:global-method-security pre-post-annotations="enabled"/>
So I did, but the authorization checks did not kick in at all.
I have been struggling with this for a while until I stumbled over this post on stackoverflow. The solution is as simple as it is strange, I only needed to move the line <sec:global-method-security pre-post-annotations="enabled"/> from the security-context.xml to the dispatcher-servlet.xml. Nowhere else can I find this suggestion, even in Peter Mularien's book its written as:
Instructing Spring Security to use method annotations
We'll also need to make a one-time change to dogstore-security.xml, where we've got the rest of our Spring Security configuration. Simply add the following element right before the <http> declaration:

<global-method-security pre-post-annotations="enabled"/>

It seems that the above declaration registers a bean post-processor that is applied to the context in which it is declared, that means that declaring it in the security context, the post-processor is not applied to beans declared in the web context.