Friday, 5 October 2012

Container Based Authentication with Spring Security

There are occasions where applications may be required to delegate authentication to a central identity access manager or Single Sign On (SSO) provider. This post describes how an application can use Spring Security for authorization but also delegate authentication to the application server.

These scenarios are referred to as "pre-authenticated" as the user has been reliably authenticated prior to accessing the application. Spring Security provides a number of classes to support pre-authentication such as the PreAuthenticatedAuthenticationProvider class. This post demonstrates how to convert a web application based on Spring Security authentication to one that uses Container based authentication.


    <sec:authentication-manager alias="authenticationManager">
        <sec:authentication-provider ref='preAuthenticatedAuthenticationProvider'/>
    </sec:authentication-manager>

    <bean id="preAuthenticatedAuthenticationProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
        <property name="preAuthenticatedUserDetailsService" ref="preAuthenticatedUserDetailsService"/>
    </bean>

    <bean id="preAuthenticatedUserDetailsService"
            class="org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesUserDetailsService"/>



As Spring Security will no longer be authenticating the user, we need to modify our login page to target the standard j_security_check URI instead of the Spring j_spring_security_check.
We also need to specify the security constraints within the web.xml such as form-based login and the security role names.

Note that although we will be using Spring Security for specifying our access control rules, we still need to specify some access control within the web.xml so that the JEE container knows when to request authentication from the user.


    <filter>
        <!-- <filter-name>springSecurityFilterChain</filter-name> -->
        <filter-name>filterChainProxy</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>

    <filter-mapping>
        <!-- <filter-name>springSecurityFilterChain</filter-name> -->
        <filter-name>filterChainProxy</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

       <!-- for container based authentication -->       
    <login-config>
        <auth-method>FORM</auth-method>
        <realm-name>file</realm-name>
       <form-login-config>
              <form-login-page>/login</form-login-page>
              <form-error-page>/login</form-error-page>
       </form-login-config>       
    </login-config>

    <security-role>
        <role-name>ROLE_CONSUMER_SUPPORT</role-name>
    </security-role>
    <security-role>
        <role-name>ROLE_CONSUMER_SUPPORT_READ_ONLY</role-name>
    </security-role>
    
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>All areas</web-resource-name>
            <url-pattern>/users/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>ROLE_CONSUMER_SUPPORT_READ_ONLY</role-name>
            <role-name>ROLE_CONSUMER_SUPPORT</role-name>
        </auth-constraint>
    </security-constraint>
 

We have also replaced the springSecurityFilterChain bean with the filterChainProxy bean.This allows specifying (via the Spring context file) which Spring bean filters will be involved in the security process.

Note
See the source code of Spring Security Config's HttpSecurityBeanDefinitionParser#registerFilterChainProxyIfNecessary method to understand how springSecurityFilterChain is used as the default bean id for the FilterChainProxy when using the http security namespace.

So which filters are required to configure the FilterChainProxy for this container based pre-authenticated scenario?
<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">

    <sec:filter-chain-map path-type="ant">
        <sec:filter-chain pattern="/**" filters="sif,j2eePreAuthFilter,logoutFilter,etf,fsi"/>
    </sec:filter-chain-map>
</bean>
The above shows five filters that have been specified in the Spring Security pre-auth sample. Lets see what each one is responsible for.


<bean id="sif" class="org.springframework.security.web.context.SecurityContextPersistenceFilter"/>

<bean id="j2eePreAuthFilter" class="org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="authenticationDetailsSource">
            <bean class="org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource">
                <property name="mappableRolesRetriever">
                    <bean class="org.springframework.security.web.authentication.preauth.j2ee.WebXmlMappableAttributesRetriever" />
                </property>
                <property name="userRoles2GrantedAuthoritiesMapper">
                    <bean class="org.springframework.security.core.authority.mapping.SimpleAttributes2GrantedAuthoritiesMapper">
                        <property name="convertAttributeToUpperCase" value="true"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>
The SecurityContextPersistenceFilter is responsible for populating the SecurityContextHolder and is required to be the first filter to execute.
The J2eePreAuthFilter is what retrieves the JEE user principle name and associated roles for the pre-authenticated principle. As the container has performed the authentication, the user principle is available with associated roles and these are mapped to Spring's GrantedAuthority instances.

<bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">

    <constructor-arg value="/"/>
    <constructor-arg>
        <list>
            <bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
        </list>
    </constructor-arg>
</bean>
The LogoutFilter will invoke the configured list of LogoutHandler implementations and then direct the user to the url specified in the first argument. It is also possible to provide a LogoutSuccessHandler to implement any custom logic after a successful logout. The LogoutHandler's are responsible for implementing the actual logout behaviour such as invalidating the session.

<bean id="preAuthenticatedProcessingFilterEntryPoint"            class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>


<bean id="etf" class="org.springframework.security.web.access.ExceptionTranslationFilter">
    <property name="authenticationEntryPoint" ref="preAuthenticatedProcessingFilterEntryPoint"/>
</bean>
The ExceptionTranslationFilter is responsible for handling any AccessDeniedException and AuthenticationException's thrown within the filter chain and mapping them to HTTP responses. It will delegate to the authenticationEntryPoint on an AuthenticationException. In the pre-authenticated scenario, the authenticationEntryPoint will simply return the HTTP error code 403 rather than commence the re-authentication process.

<bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter"/>


<bean id="httpRequestAccessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
    <property name="allowIfAllAbstainDecisions" value="false"/>
    <property name="decisionVoters">
        <list>
            <ref bean="roleVoter"/>
        </list>
    </property>
</bean>

<bean id="fsi" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
    <property name="authenticationManager" ref="authenticationManager"/>
    <property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
    <property name="securityMetadataSource">
        <sec:filter-invocation-definition-source>
            <sec:intercept-url pattern="/secure/extreme/**" access="ROLE_SUPERVISOR"/>
            <sec:intercept-url pattern="/secure/**" access="ROLE_USER"/>
            <sec:intercept-url pattern="/**" access="ROLE_USER"/>
        </sec:filter-invocation-definition-source>
    </property>
</bean>
The final filter in the chain is the FilterSecurityInterceptor that implements the access control rules for HTTP resources similar to the ones defined in the HTTP security namespace. The FilterSecurityInterceptor will first check if authentication is required, and then ask the accessDecisionManager to decide whether to grant access to a request resource. The AccessDecisionManager collaborates with the configured list of AccessDecisionVoters to resolve the authorization request.

These filters and some changes to the web.xml and login, logout forms enable transitioning a web application based on Spring Security authentication to one that uses container based authentication.

Finally, depending on the application server used, there will be some configuration required for mapping the JEE security roles.
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
<glassfish-web-app>

       <context-root>preauth</context-root>
       <security-role-mapping>
              <role-name>ROLE_USER</role-name>
              <group-name>users</group-name>
       </security-role-mapping>
       <security-role-mapping>
              <role-name>ROLE_SUPERVISOR</role-name>
              <group-name>managers</group-name>
       </security-role-mapping>  
</glassfish-web-app>

glassfish-web.xml

No comments:

Post a Comment