Spring 安全 4.0.2- 多重身份验证管理器 - 创建名称为 'org.springframework.security.filterChainProxy' 的 bean 时出错

Spring Security 4.0.2- Multiple Authentication-manager -Error creating bean with name 'org.springframework.security.filterChainProxy'

一段时间以来,我们一直在 spring 安全性中使用单个身份验证管理器 (LDAP),现在我们需要两个身份验证管理器,一个用于登录 LDAP,另一个用于基于 IP 的安全性。由于基于 IP 的安全性用作全局过滤器,LDAP 仅用于登录。因此有两个身份验证管理器。 我们尝试了类似问题的解决方案,但仍然面临同样的问题。

错误代码是:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.filterChainProxy': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: A universal match pattern ('/**') **is defined before other patterns in the filter chain, causing them to be ignored. Please check the ordering in your namespace or FilterChainProxy bean configuration

Web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">

<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>

<!-- needed for ContextLoaderListener -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        /WEB-INF/spring/application-context.xml
        /WEB-INF/spring/security-context.xml
    </param-value>
</context-param>

<!-- Bootstraps the root web application context before servlet initialization -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
    <servlet-name>dispatcher-servlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>  
        <param-value>/WEB-INF/spring/dispatcher-servlet.xml</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcher-servlet</servlet-name> 
    <url-pattern>/</url-pattern>
</servlet-mapping>

申请Context.xml

 <?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:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.8.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">

        <tx:annotation-driven/>

    <jdbc:embedded-database id="datasource" type="H2">
        <jdbc:script location="classpath:init.sql"/>    
    </jdbc:embedded-database>

    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="datasource"/>  
        <property name="persistenceUnitName" value="autoservice"/>
    </bean> 

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>

    <jpa:repositories base-package="com.oreilly.security.domain.repositories"/> 

Security.xml

<?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"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">

    <context:component-scan base-package="com.oreilly.security"/>
    <bean class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler" />

        <security:http use-expressions="false" authentication-manager-ref="LDAPAuth">
        <security:form-login login-page="/login"
            login-processing-url="/login" username-parameter="custom_username"
            password-parameter="custom_password" default-target-url="/appointments/"
            always-use-default-target="true" authentication-failure-url="/login?error=true" />

        <security:logout logout-url="/logout"
            logout-success-url="/login?logout=true" />

        <security:intercept-url pattern="/appointments/*"
            access="ROLE_USER,ROLE_ADMIN" />
        <security:intercept-url pattern="/schedule/*"
            access="ROLE_ADMIN" />

    </security:http>

    <security:http use-expressions="false" authentication-manager-ref="IpAuth">
        <security:form-login login-page="/login"
            login-processing-url="/login" username-parameter="custom_username"
            password-parameter="custom_password" default-target-url="/appointments/"
            always-use-default-target="true" authentication-failure-url="/login?error=true" />

        <security:logout logout-url="/logout"
            logout-success-url="/login?logout=true" />

        <security:intercept-url pattern="/**"
            access="ROLE_USER,ROLE_ADMIN" />    
    </security:http>



    <security:authentication-manager id ="IpAuth"> 
        <security:authentication-provider ref="customAuthenticationProvider"/>  
    </security:authentication-manager>

    <security:authentication-manager id = "LDAPAuth"> 
        <security:ldap-authentication-provider user-search-filter="(uid={0})" 
            group-search-base="ou=groups" group-search-filter="(uniqueMember={0})" 
            server-ref="ldapServer" user-context-mapper-ref="contextMapper" role-prefix="ROLE_" 
            group-role-attribute="cn"/>
    </security:authentication-manager>

    <security:ldap-server id="ldapServer" url="ldap://localhost:10389/dc=oreilly,dc=com"
        manager-dn="uid=admin,ou=system" manager-password="secret"/>

</beans>

请告诉我们如何解决这个问题,已经卡了好几天:-)

告诉您的是,您的安全配置中有多个通用模式匹配 http 部分:

<security:http use-expressions="false" authentication-manager-ref="LDAPAuth">
        <security:form-login login-page="/login"
                login-processing-url="/login" username-parameter="custom_username"
                password-parameter="custom_password" default-target-url="/appointments/"
                always-use-default-target="true" authentication-failure-url="/login?error=true" />

          <security:logout logout-url="/logout"
                logout-success-url="/login?logout=true" />

          <security:intercept-url pattern="/appointments/*"
                access="ROLE_USER,ROLE_ADMIN" />
          <security:intercept-url pattern="/schedule/*"
                access="ROLE_ADMIN" />

</security:http>

<security:http use-expressions="false" authentication-manager-ref="IpAuth">
          <security:form-login login-page="/login"
                login-processing-url="/login" username-parameter="custom_username"
                password-parameter="custom_password" default-target-url="/appointments/"
                always-use-default-target="true" authentication-failure-url="/login?error=true" />

          <security:logout logout-url="/logout"
                logout-success-url="/login?logout=true" />

          <security:intercept-url pattern="/**"
                access="ROLE_USER,ROLE_ADMIN" />    
</security:http>

如果您想拥有多个 <security:http /> 元素,则必须对除最后一个元素之外的所有元素应用一种模式。例如:

<security:http pattern="/zone1/*" ... />
<security:http pattern="/zone2/*" ... />
...
<security:http ... /> <!-- the last one does not need to apply a pattern -->

根据您的配置,在您的第一个 security:http 元素中,您有两个 intercept-url 模式(/appointmens/**/schedule/**)。您可以尝试将正则表达式匹配器应用于第一个 security:http 元素(基于 this 答案):

<security:http request-matcher="regex" pattern="^/(appointments|schedule)(/(\S)+)+$" 
               use-expressions="false" authentication-manager-ref="LDAPAuth" >
...
</security:http>
<security:http use-expressions="false" authentication-manager-ref="IpAuth">
...
</security>
  • 请检查正则表达式以避免我可能犯的任何错字或错误。

深入查看您的配置,我发现彼此之间的唯一区别是使用了不同的 authentication-manager。也许您可以尝试在同一个管理器中混合使用两个 authentication-providers,因为 spring-security 能够同时管理多个身份验证提供程序。它能够检查对一个提供者的登录请求,然后对另一个(或其他)提供者的登录请求,然后相应地采取行动。

大概是这样的:

<?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"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">

    <context:component-scan base-package="com.oreilly.security"/>
    <bean class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler" />

    <security:http use-expressions="false" authentication-manager-ref="authenticationManager">
        <security:form-login login-page="/login"
            login-processing-url="/login" username-parameter="custom_username"
            password-parameter="custom_password" default-target-url="/appointments/"
            always-use-default-target="true" authentication-failure-url="/login?error=true" />

        <security:logout logout-url="/logout"
            logout-success-url="/login?logout=true" />

        <security:intercept-url pattern="/appointments/*"
            access="ROLE_USER,ROLE_ADMIN" />
        <security:intercept-url pattern="/schedule/*"
            access="ROLE_ADMIN" />


        <security:intercept-url pattern="/**"
            access="ROLE_USER,ROLE_ADMIN" />    
    </security:http>



    <security:authentication-manager id ="authenticationManager"> 
        <security:authentication-provider ref="customAuthenticationProvider"/>
        <security:ldap-authentication-provider user-search-filter="(uid={0})" 
            group-search-base="ou=groups" group-search-filter="(uniqueMember={0})" 
            server-ref="ldapServer" user-context-mapper-ref="contextMapper" role-prefix="ROLE_" 
            group-role-attribute="cn"/>
    </security:authentication-manager>

    <security:ldap-server id="ldapServer" url="ldap://localhost:10389/dc=oreilly,dc=com"
        manager-dn="uid=admin,ou=system" manager-password="secret"/>

</beans>

请注意,我将您的两个管理器合并为一个,其中包含两个身份验证提供程序。然后,我也将 <security:http /> 元素合并为一个(这样就不再需要对其应用任何正则表达式或任何其他模式),现在有三个 <security:intercept-url /> 元素,两个来自第一个原件 <security:http> 和第二个原件。