配置Spring Security 3.x以具有多个入口点

时间:2011-01-24 14:23:22

标签: java authentication forms-authentication spring-security

我一直在使用Spring Security 3.x处理项目的用户身份验证,到目前为止,它已经完美运行。

我最近收到了新项目的要求。在此项目中,它需要两组用户身份验证:一组用于根据LDAP对员工进行身份验证,另一组用于根据数据库对客户进行身份验证。我对如何在Spring Security中配置它感到有点困惑。

我最初的想法是创建一个包含以下字段的登录屏幕: -

  • 单选按钮字段 - 供用户选择是员工还是客户。
  • j_username用户字段。
  • j_password密码字段。

如果用户选择“employee”,那么我希望Spring Security针对LDAP对其进行身份验证,否则将根据数据库对凭据进行身份验证。但是,问题是表单将提交给/j_spring_security_check,并且我无法将单选按钮字段发送到我实现的自定义身份验证提供程序。我最初的想法是,我可能需要两个表单提交URL,而不是依赖于默认的/j_spring_security_check。每个URL都将由不同的身份验证提供程序处理,但我不确定如何在Spring Security中配置它。

我知道在Spring Security中,我可以配置后备身份验证,例如,如果LDAP身份验证失败,那么它将回退到数据库身份验证,但这不是我在这个新项目中拍摄的内容。

有人可以分享我在Spring Security 3.x中应该如何配置它吗?

谢谢。


更新 - 01-28-2011 - @ EasyAngel的技术

我正在尝试执行以下操作: -

  • 员工表单登录提交至/j_spring_security_check_for_employee
  • 客户表单登录提交至/j_spring_security_check_for_customer

我想要2个不同的表单登录的原因是允许我根据用户不同地处理身份验证,而不是进行后备身份验证。在我的情况下,员工和客户可能拥有相同的用户ID。

我合并了@ EasyAngel的想法,但必须更换一些已弃用的类。我目前面临的问题是过滤进程URLS似乎都没有在Spring Security中注册,因为我不断获得Error 404: SRVE0190E: File not found: /j_spring_security_check_for_employee。我的直觉是springSecurityFilterChain bean未正确连接,因此根本不使用我的自定义过滤器。

顺便说一下,我正在使用WebSphere,并且我在服务器中设置了com.ibm.ws.webcontainer.invokefilterscompatibility=true属性。我可以毫无问题地点击默认/j_spring_security_check

这是我完整的安全配置: -

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

    <sec:http auto-config="true">
        <sec:form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?login_error=1" default-target-url="/welcome.jsp"
            always-use-default-target="true" />
        <sec:logout logout-success-url="/login.jsp" />
        <sec:intercept-url pattern="/employee/**" access="ROLE_EMPLOYEE" />
        <sec:intercept-url pattern="/customer/**" access="ROLE_CUSTOMER" />
        <sec:intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
    </sec:http>

    <bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
        <sec:filter-chain-map path-type="ant">
            <sec:filter-chain pattern="/**" filters="authenticationProcessingFilterForEmployee, authenticationProcessingFilterForCustomer" />
        </sec:filter-chain-map>
    </bean>

    <bean id="authenticationProcessingFilterForEmployee" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
        <property name="authenticationManager" ref="authenticationManagerForEmployee" />
        <property name="filterProcessesUrl" value="/j_spring_security_check_for_employee" />
    </bean>

    <bean id="authenticationProcessingFilterForCustomer" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
        <property name="authenticationManager" ref="authenticationManagerForCustomer" />
        <property name="filterProcessesUrl" value="/j_spring_security_check_for_customer" />
    </bean>

    <bean id="authenticationManagerForEmployee" class="org.springframework.security.authentication.ProviderManager">
        <property name="providers">
            <list>
                <ref bean="employeeCustomAuthenticationProvider" />
            </list>
        </property>
    </bean>

    <bean id="authenticationManagerForCustomer" class="org.springframework.security.authentication.ProviderManager">
        <property name="providers">
            <list>
                <ref bean="customerCustomAuthenticationProvider" />
            </list>
        </property>
    </bean>

    <bean id="employeeCustomAuthenticationProvider" class="ss.EmployeeCustomAuthenticationProvider">
        <property name="userDetailsService">
            <bean class="ss.EmployeeUserDetailsService"/>
        </property>
    </bean>

    <bean id="customerCustomAuthenticationProvider" class="ss.CustomerCustomAuthenticationProvider">
        <property name="userDetailsService">
            <bean class="ss.CustomerUserDetailsService"/>
        </property>
    </bean>

    <sec:authentication-manager>
        <sec:authentication-provider ref="employeeCustomAuthenticationProvider" />
        <sec:authentication-provider ref="customerCustomAuthenticationProvider" />
    </sec:authentication-manager>

</beans>

我在这里开始获得赏金,因为我似乎已经好几天都无法工作了......沮丧就是这个词。我希望有人会指出问题,或者你是否可以告诉我一个更好或更清洁的方法来处理这个问题(在代码中)。

我正在使用Spring Security 3.x.

谢谢。


更新01-29-2011 - @ Ritesh的技巧

好的,我设法让@ Ritesh的方法与我想要的非常接近。我有radiobutton,允许用户选择他们是客户还是员工。看起来这种方法运作得相当好,有一个问题......

  • 如果员工使用正确的凭证登录,则允许他们使用... 按预计工作
  • 如果员工使用错误的凭据登录,则不允许他们使用... 按预计工作
  • 如果客户使用正确的凭据登录,则允许他们使用... 按预计工作
  • 如果客户使用错误的凭据登录,则身份验证会回退到员工身份验证... 无法正常工作。这是有风险的,因为如果我选择客户身份验证,并将其打入员工凭证,它也会允许用户进入,这不是我想要的。
    <sec:http auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint">
        <sec:logout logout-success-url="/login.jsp"/>
        <sec:intercept-url pattern="/employee/**" access="ROLE_EMPLOYEE"/>
        <sec:intercept-url pattern="/customer/**" access="ROLE_CUSTOMER"/>
        <sec:intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>

        <sec:custom-filter position="FORM_LOGIN_FILTER" ref="myAuthenticationFilter"/>
    </sec:http>


    <bean id="myAuthenticationFilter" class="ss.MyAuthenticationFilter">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="authenticationFailureHandler" ref="failureHandler"/>
        <property name="authenticationSuccessHandler" ref="successHandler"/>
    </bean>

    <bean id="loginUrlAuthenticationEntryPoint"
          class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
        <property name="loginFormUrl" value="/login.jsp"/>
    </bean>

    <bean id="successHandler"
          class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
        <property name="defaultTargetUrl" value="/welcome.jsp"/>
        <property name="alwaysUseDefaultTargetUrl" value="true"/>
    </bean>

    <bean id="failureHandler"
          class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
        <property name="defaultFailureUrl" value="/login.jsp?login_error=1"/>
    </bean>


    <bean id="employeeCustomAuthenticationProvider" class="ss.EmployeeCustomAuthenticationProvider">
        <property name="userDetailsService">
            <bean class="ss.EmployeeUserDetailsService"/>
        </property>
    </bean>

    <bean id="customerCustomAuthenticationProvider" class="ss.CustomerCustomAuthenticationProvider">
        <property name="userDetailsService">
            <bean class="ss.CustomerUserDetailsService"/>
        </property>
    </bean>


    <sec:authentication-manager alias="authenticationManager">
        <sec:authentication-provider ref="customerCustomAuthenticationProvider"/>
        <sec:authentication-provider ref="employeeCustomAuthenticationProvider"/>
    </sec:authentication-manager>
</beans>

这是我更新的配置。它必须是我需要做的一些非常小的调整,以防止身份验证回落,但我现在似乎无法弄明白。

谢谢。

更新 - 解决@ Ritesh的技术

好的,我想我已经解决了这个问题。我没有让EmployeeCustomAuthenticationProvider依赖默认的UsernamePasswordAuthenticationToken,而是为它创建了EmployeeUsernamePasswordAuthenticationToken,就像我为CustomerUsernamePasswordAuthenticationToken创建的CustomerCustomAuthenticationProvider一样。然后,这些提供商将覆盖supports(): -

CustomerCustomAuthenticationProvider类

@Override
public boolean supports(Class<? extends Object> authentication) {
    return (CustomerUsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}

EmployeeCustomAuthenticationProvider类

@Override
public boolean supports(Class<? extends Object> authentication) {
    return (EmployeeUsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}

MyAuthenticationFilter类

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

    ...

    UsernamePasswordAuthenticationToken authRequest = null;

    if ("customer".equals(request.getParameter("radioAuthenticationType"))) {
        authRequest = new CustomerUsernamePasswordAuthenticationToken(username, password);

    }
    else {
        authRequest = new EmployeeUsernamePasswordAuthenticationToken(username, password);
    }

    setDetails(request, authRequest);

    return super.getAuthenticationManager().authenticate(authRequest);
}

...和WALAA!经过几天的挫折,它现在完美地运作了!

希望这篇文章能够帮助那些和我在一起做同样事情的人。

5 个答案:

答案 0 :(得分:24)

您无需创建/j_spring_security_check_for_employee/j_security_check_for_customer filterProcessingUrl

默认的一个可以正常使用单选按钮字段构思。

在自定义登录LoginFilter中,您需要为员工和客户创建不同的令牌。

以下是步骤:

  1. 使用默认UsernamePasswordAuthenticationToken进行员工登录。

  2. 为客户登录创建CustomerAuthenticationToken。扩展AbstractAuthenticationToken以使其类类型与UsernamePasswordAuthenticationToken不同。

  3. 定义自定义登录过滤器:

    <security:http>
        <security:custom-filter position="FORM_LOGIN_FILTER" ref="customFormLoginFilter" />
    </security:http>
    
  4. customFormLoginFilter中,按如下方式覆盖attemptAuthentication(伪代码):

    if (radiobutton_param value employee) {
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        setDetails(whatever);
        return getAuthenticationManager().authenticate(authRequest);
    } else if (radiobutton_param value customer) {
        CustomerAuthenticationToken authRequest = new CustomerAuthenticationToken(username, password);
        setDetails(whatever);
        return getAuthenticationManager().authenticate(authRequest);
    }
    
  5. 覆盖supports中的EmployeeCustomAuthenticationProvider方法以支持UsernamePasswordAuthenticationToken

  6. 覆盖supports中的CustomerCustomAuthenticationProvider方法以支持CustomerAuthenticationToken

    @Override
    public boolean supports(Class<?> authentication) {
        return (CustomerAuthenticationToken.class.isAssignableFrom(authentication));
    }
    
  7. authentication-manager中使用这两个提供商:

    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider ref='employeeCustomAuthenticationProvider ' />
        <security:authentication-provider ref='customerCustomAuthenticationProvider ' />
    </security:authentication-manager>
    

答案 1 :(得分:5)

您可以定义多个AuthenticationProcessingFilter过滤器。他们每个人都可以拥有不同的网址,例如 / j_security_check_for_employee / j_security_check_for_customer 。以下是演示此想法的安全应用程序上下文的示例:

<bean id="myfilterChainProxy" class="org.springframework.security.util.FilterChainProxy">
     <security:filter-chain-map pathType="ant">
         <security:filter-chain pattern="/**" filters="authenticationProcessingFilterForCustomer, authenticationProcessingFilterForEmployee, ..." />
     </security:filter-chain-map>
</bean>


<bean id="authenticationProcessingFilterForCustomer" class="org.springframework.security.web.authentication.AuthenticationProcessingFilter">
    <property name="authenticationManager" ref="authenticationManagerForCustomer"/>
    <property name="filterProcessesUrl" value="/j_security_check_for_customer"/>
</bean>

<bean id="authenticationProcessingFilterForEmployee" class="org.springframework.security.web.authentication.AuthenticationProcessingFilter">
    <property name="authenticationManager" ref="authenticationManagerForEmployee"/>
    <property name="filterProcessesUrl" value="/j_security_check_for_employee"/>
</bean>

<bean id="authenticationManagerForCustomer" class="org.springframework.security.authentication.ProviderManager">
    <property name="providers">
        <list>
            <bean class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
                <property name="userDetailsService">
                    <ref bean="customerUserDetailsServiceThatUsesDB"/>
                </property>
            </bean>
        </list>
    </property>
</bean>

<bean id="authenticationManagerForEmployee" class="org.springframework.security.authentication.ProviderManager">
    <property name="providers">
        <list>
            <bean class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
                <property name="userDetailsService">
                    <ref bean="employeeUserDetailsServiceThatUsesLDAP"/>
                </property>
            </bean>
        </list>
    </property>
</bean>

正如您所看到的,在这种情况下,您还有不同的UserDetailService s - 用于数据库身份验证和LDAP。

我认为为客户和员工提供不同的身份验证URL是个好主意(特别是如果他们使用不同的身份验证策略)。您甚至可以为它们设置不同的登录页面。

答案 2 :(得分:1)

有关Java配置参考

当我热衷于在此处编写实现相同技术的java配置方法来帮助不熟悉xml配置但我不想用这么长的java配置代码来劫持线程美的人。

希望通过 java配置(基于注释)实现相同目的的人可以参考下面给出的我自己回答的问题链接,也可以在答案中找到我的github存储库链接。

对于基于注释的配置代码,请在这里参考我的答案 Multiple AuthenticationProvider with different UsernamePasswordAuthToken to authenticate different login forms without fallback authentication

答案 3 :(得分:0)

您可以将此信息存储在DB中。例如,您可以在ldap_auth表中使用名为Users的列。您可以查看我的其他答案(例如):

Spring login form example

如果仔细查看UserService类,您会注意到我实际测试了此LDAP标志并从LDAP或数据库中获取用户密码。

答案 4 :(得分:0)

再次是我:)你可以尝试使用这样的过滤器:

<sec:http auto-config="true">
    ...
    <sec:custom-filter ref="authenticationProcessingFilterForCustomer" after="FIRST"/>
    <sec:custom-filter ref="authenticationProcessingFilterForEmployee" after="FIRST"/>
</sec:http>

而不是定义bean springSecurityFilterChain