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

StackOverflow https://stackoverflow.com/questions/19687697

سؤال

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

هل كانت مفيدة؟

المحلول

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:

  • As shown in the example, use the id property on sec:authentication-manager because using alias will override the previous bean definition and assign another pointer to it
  • As shown in the example, ensure to update the authenticationManager reference in employeeFilter and customerFilter
  • As shown in the example, ensure to specify the http@authentication-manager-ref attribute pointing to an AuthenticationManager that can authenticate both users. This is necessary for things like the FilterSecurityInterceptor.
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top