Thursday, February 27, 2014

Spring Security Custom FilterChainProxy Configuration

I have worked with Spring Security extensively for the past six months as part of my current job. But my work mostly involved configuring the most important FilterChainProxy of Spring Security using namespaces. But I wanted to dig deeper and to see how the internals of the Spring Security FilterChainProxy so I completely read the Spring Security Documentation for version 3.1.4.RELEASE and realized that in fact I can configure a custom FilterChainProxy given that I define the following filters which are a must have for the FilterChainProxy.
  1. SecurityContextPersistenceFilter
  2. ExceptionTranslationFilter
  3. FilterSecurityInterceptor
The document had a small snippet of declaring these inside of the custom FilterChainProxy but that didn't show the complete set of beans required to get the FilterChainProxy working. So I wanted to get the bottom of it so I configured it myself by going through documentation.

Basically you can have a working FilterChainProxy with the name springSecurityFilterChain by just using the following Spring Declarations using security namespace.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

     <security:http security="none" pattern="/resources/**" />
     <security:http auto-config="true" use-expressions="true">
        <security:intercept-url pattern="/login*"
            access="isAnonymous()" />
        <security:intercept-url pattern="/**"
            access="isAuthenticated()" />
        <security:form-login login-page="/login"
            default-target-url="/" always-use-default-target="true"
            authentication-failure-url="/login?error=true" />
        <security:logout invalidate-session="true"
            delete-cookies="JSESSIONID" logout-success-url="/login" />
    </security:http>

    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider
            user-service-ref="userService">
            <security:password-encoder hash="md5"
                base64="true">
                <security:salt-source user-property="salt" />
            </security:password-encoder>
        </security:authentication-provider>
    </security:authentication-manager>
    
</beans>

But as you can see here I have set auto-config attribute of security:http namespace to true in order to generate all the required configurations of Filters for me. This is easy but not the most flexible declaration.

So the same thing can be declared explicitly without using the security namespace as follows.


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider
            user-service-ref="userService">
            <security:password-encoder hash="md5"
                base64="true">
                <security:salt-source user-property="salt" />
            </security:password-encoder>
        </security:authentication-provider>
    </security:authentication-manager>

    <bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
        <constructor-arg>
            <list>
                <security:filter-chain pattern="/login**" filters="none"/>
                <security:filter-chain pattern="/resources/**" filters="none"/>
                <security:filter-chain pattern="/**"
                    filters="securityContextPersistenceFilterWithASCTrue,    
                                                          logoutFilter,                                                
                                                             formLoginFilter,                                                              
                                                             formLoginExceptionTranslationFilter,
                                                             filterSecurityInterceptor" />
            </list>
        </constructor-arg>
    </bean>

    <bean id="securityContextPersistenceFilterWithASCTrue"
        class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
        <constructor-arg>
            <bean class="org.springframework.security.web.context.HttpSessionSecurityContextRepository"/>
        </constructor-arg>
    </bean>
    
    <bean id="formLoginExceptionTranslationFilter"
        class="org.springframework.security.web.access.ExceptionTranslationFilter">
        <constructor-arg>
            <bean
                class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
                <constructor-arg value="/login"/>                
            </bean>
        </constructor-arg>
        <property name="accessDeniedHandler">
            <bean
                class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
                <property name="errorPage" value="/exception" />
            </bean>
        </property>
    </bean>

    <bean id="formLoginFilter"
        class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="allowSessionCreation" value="true"/>
        <property name="authenticationSuccessHandler">
            <bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler">
                <constructor-arg value="/"/>
                <property name="alwaysUseDefaultTargetUrl" value="true"/>
            </bean>
        </property>
        <property name="authenticationFailureHandler">
            <bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
                <constructor-arg value="/login?error=true"/>
            </bean>
        </property>
    </bean>

    <bean id="filterSecurityInterceptor"
        class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
        <property name="authenticationManager" ref="authenticationManager" />
        <property name="accessDecisionManager" ref="accessDecisionManager" />
        <property name="runAsManager" ref="runAsManager" />
        <property name="securityMetadataSource">
            <security:filter-security-metadata-source use-expressions="true">
                <security:intercept-url pattern="/**"
                    access="isAuthenticated()" />
            </security:filter-security-metadata-source>
        </property>
    </bean>

    <bean id="accessDecisionManager"
        class="org.springframework.security.access.vote.AffirmativeBased">
        <constructor-arg>
            <list>
                <bean class="org.springframework.security.access.vote.RoleVoter"/>
                <bean class="org.springframework.security.web.access.expression.WebExpressionVoter"/>
            </list>
        </constructor-arg>
        <property name="allowIfAllAbstainDecisions" value="false"/>
    </bean>

    <bean id="runAsManager"
        class="org.springframework.security.access.intercept.RunAsManagerImpl">
        <property name="key" value="TELCO_RUN_AS"/>
    </bean>
        
    <bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
        <constructor-arg value="/login"/>        
        <constructor-arg>
            <list>
                <bean class="org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler">
                    <constructor-arg>
                        <list>
                            <value>JSESSIONID</value>
                        </list>                        
                    </constructor-arg>
                </bean>
                <bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
            </list>
        </constructor-arg>
    </bean>
    
</beans>


Wow Spring Security namespace is a real life saver when it comes to configuring right? but critics might ask why declare explicitly when it generates.

  1. Gives more control over the configurations
  2. Enables to understand the flow
  3. Enables flexibility
For this Custom FilterChainProxy to work it must be named similar to the filter name of DelegatingFilterProxy in the web.xml configuration, for example with the following configuration;

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

    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
The explicitly declared FilterChainProxy must be named springSecurityFilterChain.

Trackbacks/Pings

2 comments:

  1. You might also look at the Java Configuration support which allows much more flexibility, but also keeps things simple.

    ReplyDelete
  2. Hi Rob,

    Yes I have been using Java Configuration support, but not to do this. Anyway I prefer XML Configurations for this because I don't need the project to be compiled just to change the configuration.

    ReplyDelete