spring-securityrequest-headerspre-authentication

How to delegate an authentication-manager to a specific CustomUserDetailsService, based on the RequestHeaderAuthenticationFilter?


I'm having a problem pre-authenticating users in a spring based app.

Here is my scenario. I have two CustomUserDetailsServices, one for Employees and one for Customers. In front of my server it is implemented a proxy, that adds to the Http Header of the client request two header informations. Let's call theam Header_A and Header_B.

I'd like that spring security to do the following steps:

  1. Take the Header_A and use it in the EmployeeUserDetailsService, to call the loadUserByUsername(String name) method. If the user is found, authenticate it and give him access to the app.
  2. If the user is not found(it is not identified as an employee), i'd like that security to take the Header_B and use it in the CustomerUserDetailsService.

I've tried the following, and it works, but with a work-around(I don't want to do that work-around):

<sec:http use-expressions="true" access-denied-page="/denied.jsp" entry-point-ref="http403EntryPoint">
    <sec:intercept-url pattern="/**" access="hasRole('ROLE_USER')" /> 
    <sec:custom-filter after="PRE_AUTH_FILTER" ref="customerFilter" />
    <sec:custom-filter position="PRE_AUTH_FILTER" ref="employeeFilter" />
    <sec:logout delete-cookies="true" invalidate-session="true" logout-success-url="/" />
</sec:http>
<bean id="employeeFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter">
    <property name="principalRequestHeader" value="Header_A"/>
    <property name="authenticationManager" ref="authenticationManager" />
    <property name="exceptionIfHeaderMissing" value="false"/>
</bean> 
<bean id="customerFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter">
    <property name="principalRequestHeader" value="Header_B"/>
    <property name="authenticationManager" ref="authenticationManager" />
    <property name="exceptionIfHeaderMissing" value="false"/>
</bean>
<bean id="employeePreAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
    <property name="throwExceptionWhenTokenRejected" value="false" />
    <property name="preAuthenticatedUserDetailsService">
        <bean id="userDetailsServiceWrapper"  class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
            <property name="userDetailsService" ref="employeeUserDetailsService"/>
        </bean>
    </property>
</bean>
<bean id="customerPreAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
    <property name="throwExceptionWhenTokenRejected" value="false" />
    <property name="preAuthenticatedUserDetailsService">
        <bean id="userDetailsServiceWrapper"  class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
            <property name="userDetailsService" ref="customerUserDetailsService"/>
        </bean>
    </property>
</bean>
<sec:authentication-manager alias="authenticationManager">
    <sec:authentication-provider ref="employeePreAuthProvider" />
    <sec:authentication-provider ref="customerPreAuthProvider" />
</sec:authentication-manager>    
<bean id="customerUserDetailsService" class="xxx.CustomerUserDetailsService"/>
<bean id="employeeUserDetailsService" class="xxx.EmployeeUserDetailsService"/>

This thing does the following:

  1. Takes Header_A and use it in EmployeeUserDetailsService
  2. Takes Header_B and use it in EmployeeUserDetailsService
  3. Takes Header_A and use it in CustomerUserDetailsServie
  4. Takes Header_B and use it in CustomerUserDetailsServie

The workaround that I do is to verify the length of the headers(that is fix), and to return; if one header get's in the wrong UserDetailsService


Solution

  • If you want to only use the EmployeeUserDetailsService for Header_A and the CustomerUserDetailsService for Header_B, then you can create multiple AuthenticationManager instances and wire them in the corresponding filters. For example:

    <sec:http use-expressions="true" access-denied-page="/denied.jsp" entry-point-ref="http403EntryPoint" authentication-manager-ref="authenticationManager">
        <sec:intercept-url pattern="/**" access="hasRole('ROLE_USER')" /> 
        <sec:custom-filter after="PRE_AUTH_FILTER" ref="customerFilter" />
        <sec:custom-filter position="PRE_AUTH_FILTER" ref="employeeFilter" />
        <sec:logout delete-cookies="true" invalidate-session="true" logout-success-url="/" />
    </sec:http>
    <bean id="employeeFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter">
        <property name="principalRequestHeader" value="Header_A"/>
        <property name="authenticationManager" ref="employeeAuthenticationManager" />
        <property name="exceptionIfHeaderMissing" value="false"/>
    </bean> 
    <bean id="customerFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter">
        <property name="principalRequestHeader" value="Header_B"/>
        <property name="authenticationManager" ref="customerAuthenticationManager" />
        <property name="exceptionIfHeaderMissing" value="false"/>
    </bean>
    <bean id="employeePreAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
        <property name="throwExceptionWhenTokenRejected" value="false" />
        <property name="preAuthenticatedUserDetailsService">
            <bean id="userDetailsServiceWrapper"  class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
                <property name="userDetailsService" ref="employeeUserDetailsService"/>
            </bean>
        </property>
    </bean>
    <bean id="customerPreAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
        <property name="throwExceptionWhenTokenRejected" value="false" />
        <property name="preAuthenticatedUserDetailsService">
            <bean id="userDetailsServiceWrapper"  class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
                <property name="userDetailsService" ref="customerUserDetailsService"/>
            </bean>
        </property>
    </bean>
    <sec:authentication-manager id="authenticationManager">
        <sec:authentication-provider ref="customerPreAuthProvider" />
        <sec:authentication-provider ref="employeePreAuthProvider" />
    </sec:authentication-manager>
    <sec:authentication-manager id="customerAuthenticationManager">
        <sec:authentication-provider ref="customerPreAuthProvider" />
    </sec:authentication-manager>
    <sec:authentication-manager id="employeeAuthenticationManager">
        <sec:authentication-provider ref="employeePreAuthProvider" />
    </sec:authentication-manager>    
    <bean id="customerUserDetailsService" class="xxx.CustomerUserDetailsService"/>
    <bean id="employeeUserDetailsService" class="xxx.EmployeeUserDetailsService"/>
    

    A few notes: