【问题标题】:Spring Security Java - Multiple Authentication Manager - 2 bean found errorSpring Security Java - 多重身份验证管理器 - 发现 2 个 bean 错误
【发布时间】:2015-11-13 08:43:42
【问题描述】:

我的应用程序中有 2 个身份验证管理器。

@Configuration
@EnableWebMvcSecurity
@ComponentScan
@ImportResource("classpath:security-context.xml")
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private AuthenticationEntryPoint webAuthenticationEntryPoint = null;

    private AuthenticationManager webAuthenticationManager = null;

    private final webPreAuthenticatedProcessingFilter webPreAuthenticatedProcessingFilter;


    public WebSecurityConfig() {
        super();
        webPreAuthenticatedProcessingFilter = new webPreAuthenticatedProcessingFilter();
        webPreAuthenticatedProcessingFilter.setAuthenticationManager(webAuthenticationManager);
        webPreAuthenticatedProcessingFilter.setInvalidateSessionOnPrincipalChange(true);
        webPreAuthenticatedProcessingFilter.setContinueFilterChainOnUnsuccessfulAuthentication(Boolean.FALSE);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
            .antMatchers("/login", "/logoffUser", "/sessionExpired", "/error").permitAll()
            .anyRequest().authenticated().and().rememberMe().and().httpBasic()
            .authenticationEntryPoint(webAuthenticationEntryPoint).and()
            .addFilterAfter(webPreAuthenticatedProcessingFilter, webPreAuthenticatedProcessingFilter.class).csrf()
            .disable().logout().deleteCookies("JSESSIONID").logoutSuccessUrl("/logoff").invalidateHttpSession(true);
    }

    @Autowired
    @Qualifier("webAuthManager")
    public void setwebAuthenticationManager(AuthenticationManager webAuthenticationManager) {

        this.webAuthenticationManager = webAuthenticationManager;
        webPreAuthenticatedProcessingFilter.setAuthenticationManager(this.webAuthenticationManager);
    }

    @Autowired
    public void setwebAuthenticationEntryPoint(AuthenticationEntryPoint webAuthenticationEntryPoint) {

        this.webAuthenticationEntryPoint = webAuthenticationEntryPoint;
    }

}

PreAuthenticatedFilter 类,

@Component("preAuthenticatedFilter")
public class WebPreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter {   
    @Override
    @Autowired
    @Qualifier("webAuthManager")
    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        super.setAuthenticationManager(authenticationManager);
    }
}

WebAuthenticationManager 类,

@Service
@ComponentScan
@Component("webAuthManager")
public class WebAuthenticationManager implements AuthenticationManager {


    @Override
    public Authentication authenticate(Authentication authentication) {

       // ...

    }
}

还有多了一个认证管理器的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"
    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-3.2.xsd">

    <bean id="ldapConfig" class="com.wellmanage.mnpi.core.jms.LdapConfig">
        <constructor-arg name="user" value="${ldap.conn.user}"/>
        <constructor-arg name="sid" value="${ldap.conn.sid}"/>
    </bean>

    <security:ldap-server url="${ldap.url}" 
        manager-dn="${ldap.conn.user}" manager-password="#{ldapConfig.getPassword()}" />

    <security:authentication-manager alias="ldapAuthManager" id="ldapAuthManager">
        <security:ldap-authentication-provider
            user-search-filter="(&amp;(sAMAccountName={0})(objectclass=organizationalPerson))"
            user-search-base="OU=${ldap.user-search-base.name}"
            group-search-filter="(member={0})" group-search-base="OU=Global-Groups"
            group-role-attribute="cn"/>
    </security:authentication-manager>

    <bean id="mq.accessDecisionManager"
        class="org.springframework.security.access.vote.AffirmativeBased">
        <constructor-arg>
            <list>
                <bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter"/>
            </list>
        </constructor-arg>
    </bean>
</beans>

即使我使用限定符,我也会得到以下异常。

Exception in thread "main" org.springframework.context.ApplicationContextException: Unable to start embedded container; nested exception is org.springframework.boot.context.embedded.EmbeddedServletContainerException: Unable to start embedded Tomcat
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.onRefresh(EmbeddedWebApplicationContext.java:133)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:474)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:118)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:686)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:320)
    at com.wellmanage.mnpi.Application.main(Application.java:60)
Caused by: org.springframework.boot.context.embedded.EmbeddedServletContainerException: Unable to start embedded Tomcat
    at org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer.initialize(TomcatEmbeddedServletContainer.java:98)
    at org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer.<init>(TomcatEmbeddedServletContainer.java:75)
    at org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory.getTomcatEmbeddedServletContainer(TomcatEmbeddedServletContainerFactory.java:378)
    at org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory.getEmbeddedServletContainer(TomcatEmbeddedServletContainerFactory.java:155)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.createEmbeddedServletContainer(EmbeddedWebApplicationContext.java:157)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.onRefresh(EmbeddedWebApplicationContext.java:130)
    ... 5 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springSecurityFilterChain' defined in class path resource [org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is java.lang.IllegalArgumentException: Expecting to only find a single bean for type interface org.springframework.security.authentication.AuthenticationManager, but found [webAuthManager, ldapAuthManager]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:599)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1119)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1014)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
    at org.springframework.boot.context.embedded.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:209)
    at org.springframework.boot.context.embedded.ServletContextInitializerBeans.addAsRegistrationBean(ServletContextInitializerBeans.java:165)
    at org.springframework.boot.context.embedded.ServletContextInitializerBeans.addAsRegistrationBean(ServletContextInitializerBeans.java:160)
    at org.springframework.boot.context.embedded.ServletContextInitializerBeans.addAdaptableBeans(ServletContextInitializerBeans.java:143)
    at org.springframework.boot.context.embedded.ServletContextInitializerBeans.<init>(ServletContextInitializerBeans.java:74)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.getServletContextInitializerBeans(EmbeddedWebApplicationContext.java:234)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.selfInitialize(EmbeddedWebApplicationContext.java:221)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.access$000(EmbeddedWebApplicationContext.java:84)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext$1.onStartup(EmbeddedWebApplicationContext.java:206)
    at org.springframework.boot.context.embedded.tomcat.TomcatStarter.onStartup(TomcatStarter.java:54)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5156)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1409)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1399)
    at java.util.concurrent.FutureTask.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is java.lang.IllegalArgumentException: Expecting to only find a single bean for type interface org.springframework.security.authentication.AuthenticationManager, but found [webAuthManager, ldapAuthManager]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:189)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:588)
    ... 26 more
Caused by: java.lang.IllegalArgumentException: Expecting to only find a single bean for type interface org.springframework.security.authentication.AuthenticationManager, but found [webAuthManager, ldapAuthManager]
    at org.springframework.util.Assert.isTrue(Assert.java:65)
    at org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration.lazyBean(AuthenticationConfiguration.java:112)
    at org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration.getAuthenticationMangerBean(AuthenticationConfiguration.java:122)
    at org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration.getAuthenticationManager(AuthenticationConfiguration.java:81)
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.authenticationManager(WebSecurityConfigurerAdapter.java:236)
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.getHttp(WebSecurityConfigurerAdapter.java:178)
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.init(WebSecurityConfigurerAdapter.java:283)
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.init(WebSecurityConfigurerAdapter.java:68)
    at com.wellmanage.mnpi.security.WebSecurityConfig$$EnhancerBySpringCGLIB$$c6c13351.init(<generated>)
    at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.init(AbstractConfiguredSecurityBuilder.java:367)
    at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.doBuild(AbstractConfiguredSecurityBuilder.java:320)
    at org.springframework.security.config.annotation.AbstractSecurityBuilder.build(AbstractSecurityBuilder.java:39)
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration.springSecurityFilterChain(WebSecurityConfiguration.java:98)
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$$EnhancerBySpringCGLIB$$eb0d5df1.CGLIB$springSecurityFilterChain$3(<generated>)
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$$EnhancerBySpringCGLIB$$eb0d5df1$$FastClassBySpringCGLIB$$622492d6.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:309)
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$$EnhancerBySpringCGLIB$$eb0d5df1.springSecurityFilterChain(<generated>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:162)
    ... 27 more

【问题讨论】:

  • 但是你为什么要同时有 2 个经理呢?
  • 1 个管理器使用 DB auth,另一个管理器使用 ldap。还有其他更好的方法吗?

标签: spring spring-security spring-boot


【解决方案1】:

您需要设置 2 个 Providers,一个用于 Ldap,一个用于 Web。 AuthenticationManager(如果是ProviderManager实例)会选择对应的Provider进行认证。经理基本上管理提供实际工作的提供者列表。

喜欢(我想在一个地方配置所有东西更容易?):

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth  // you have to prepare following beans also
        .authenticationProvider(getLdapAuthenticationProvider())
        .authenticationProvider(getWebAuthenticationProvider());
}

http://docs.spring.io/spring-security/site/docs/4.0.2.RELEASE/reference/htmlsingle/#core-services-authentication-manager

【讨论】:

  • 这个应该放在认证的实现里面吗?管理者将如何选择相应的提供者?
  • 1) 这是给你的WebSecurityConfig 班级的。 2) ProviderManager 会询问每个 Provider 是否支持当前请求的认证,LdapProvider 支持一个,WebProvider 支持另一个。 LdapAuthentication token 和 UsernamePasswordAuthentication,来自不同的 Filters,所以 Manager 会决定使用哪个 Provider
  • 这种方式如何通过自动布线注入ldapauth管理器?
  • 如果 bean 在 xml 配置中创建,您可以将其自动装配到 Config 中,例如 @Autowire private AuthenticationProvider ldapAuthenticationProvider。或在那里的方法中创建新的 bean(新的 LdapAuthenticationProvider 并填充所有属性)。我猜拳法是最简单的
【解决方案2】:

XML 基础解决方案: 如果您需要超过 1 个身份验证管理器,则可以定义它,但会收到警告“覆盖全局注册的身份验证管理器”,这意味着所有元素:HTTP 都与最后定义的身份验证管理器匹配。

因此,您可以针对 XML 基础和需要安全性 v4.0+ 进行尝试。

解决方案:您必须在 authenticationmanager 中使用“Id”属性,并且在“authentication-manager-ref”属性中的“HTTP”元素中定义相同的 id。因此,安全性会为特定的 HTTP 请求获取合适的身份验证管理器。

示例代码:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="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-4.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-4.0.xsd">

<!--  HTTP basic authentication for Rest API when match url pattern="/api/**"    -->

<security:http realm="API" pattern="/api/**"
    use-expressions="true" auto-config="true" create-session="stateless"
    authentication-manager-ref="APIAuthenticationManager">

    <security:intercept-url pattern="/**" access="hasRole('USER')" />
    <security:http-basic />
    <security:csrf disabled="true" />
</security:http>

<security:authentication-manager id="APIAuthenticationManager"
    alias="APIAuthenticationManager">
    <security:authentication-provider user-service-ref="APIAuthenticationProvider" />
</security:authentication-manager> 

<bean class="com.example.security.api.APIAuthentication"
    id="APIAuthenticationProvider">
</bean>


<!-- From Login authentication for web portal when match url parrern ="/web/**"    -->


<security:http realm="WEB" auto-config="true"
    use-expressions="true" create-session="always"
    authentication-manager-ref="userAuthenticationManager">

    <security:intercept-url pattern="/resources/**" access="permitAll" />
    <security:intercept-url pattern="/web/login" access="hasRole('ROLE_ANONYMOUS')" />
    <security:intercept-url pattern="/web/dashboard**" access="hasRole('USER')" method="GET" />


    <!-- Set login, landing page, Successful, unsuccessful ... -->
    <security:form-login login-page="/web/login"
        default-target-url="/web/dashboard" 
        login-processing-url="/web/dashboard"
        always-use-default-target="true"

        authentication-failure-url="/web/login?error" 
        username-parameter="email"           
        password-parameter="encryptedPassword" />

    <!-- Set logout detail -->
    <security:logout logout-url="/web/logout"
        logout-success-url="/index.jsp" 
        delete-cookies="JSESSIONID"
        invalidate-session="true" />

    <security:csrf/>
</security:http>

<security:authentication-manager id="userAuthenticationManager" alias="userAuthenticationManager">
    <security:authentication-provider user-service-ref="userAuthenticationProvider" />
</security:authentication-manager>

<bean class="com.example.security.web.UserAuthentication"
    id="userAuthenticationProvider">

</bean>    

【讨论】:

    【解决方案3】:

    接受的答案是一个正确的解决方案,但我认为了解 AuthenticationManager 如何从该提供程序列表中选择特定的 AuthenticationProvider 是件好事。在实现每个 CustomAuthenticationProvider 时,除了authenticate(Authentication auth) 方法之外,还有一个签名为boolean supports(Class&lt;?&gt; var1) 的方法,我们必须实现它。对于AuthenticationManager 中的每个provider,在调用provider.authenticate(authentication) 方法之前, authenticationManager 通过调用provider.support(authentication.getClass) 来检查它是否支持当前的provider。 从这里你会假设,支持方法的实现将是这样的

    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
    

    附上 ProviderManager 类的高亮源代码截图,这是 AuthenticationManager 的默认实现。You can find this is in the official source code too.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-12-31
      • 1970-01-01
      • 1970-01-01
      • 2017-04-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多