在 Spring Security 3.2.5 中,是什么导致了 AuthenticationManager 实现中的无限循环?

In Spring Security 3.2.5, what is causing an infinite loop inside the AuthenticationManager implementation?

不久前我遇到了一个有趣的情况,它导致 Spring 安全部门的 AuthenticationManager 出现无限循环(并最终导致堆栈溢出)。几个月来,一切都按预期工作,但后来我决定将我的 XML 配置转移到纯代码配置。这是我在 Java 配置中的基本设置:

@Configuration
@EnableWebMvcSecurity
@ComponentScan(basePackages = { "com.my.company" })
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // Disable default configuration
    public SecurityConfig() {
        super(true);
    }

    @Autowired
    AuthenticationProviderImpl authenticationProvider;

    @Autowired
    MyAuthenticationEntryPoint customAuthenticationEntryPoint;

    @Autowired
    AuthenticationTokenProcessingFilter authenticationTokenProcessingFilter;

    @Bean(name = "authenticationManager")
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {

        // Ignore requests of resources in security
        web.ignoring().antMatchers("/resources/**")
        // Ignore requests to authentication
                .and().ignoring().antMatchers("/auth/**");
    }

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

        // Define main authentication filter
        http.addFilterBefore(authenticationTokenProcessingFilter,
                UsernamePasswordAuthenticationFilter.class)

                // Request path authorization
                .authorizeRequests()
                .antMatchers("/api/**")
                .access("isAuthenticated()")

                // Authentication provider
                .and()
                .authenticationProvider(authenticationProvider)

                // Security failure exception handling
                .exceptionHandling()
                .authenticationEntryPoint(customAuthenticationEntryPoint)

                // Session Management
                .and().sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                // Default security HTTP headers
                .and().headers().xssProtection().frameOptions()
                .cacheControl().contentTypeOptions();
    }
}

但是,我很快发现此配置会导致我的 AuthenticationProviderImpl(实现 Spring 安全 AuthenticationProvider 接口)出现问题。当实现的重写 authenticate 方法抛出 BadCredentialsException 时,class 中完全相同的方法将再次永久调用,直到堆栈溢出。好消息是我通过简单地覆盖 SecurityConfig 中的 configure(AuthenticationManagerBuilder builder) 并在那里而不是 configure(HttpSecurity http) 中声明我的 AuthenticationProvider 实现来修复我的配置。这是固定版本:

@Configuration
@EnableWebMvcSecurity
@ComponentScan(basePackages = { "com.my.company" })
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // Disable default configuration
    public SecurityConfig() {
        super(true);
    }

    @Autowired
    AuthenticationProviderImpl authenticationProvider;

    @Autowired
    MyAuthenticationEntryPoint customAuthenticationEntryPoint;

    @Autowired
    AuthenticationTokenProcessingFilter authenticationTokenProcessingFilter;

    @Bean(name = "authenticationManager")
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    public void configure(AuthenticationManagerBuilder builder) {
        // Configure the authentication manager WITH the authentication
        // provider. Not overriding this method causes very bad things to
        // happen.
        builder.authenticationProvider(authenticationProvider);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {

        // Ignore requests of resources in security
        web.ignoring().antMatchers("/resources/**")
        // Ignore requests to authentication
                .and().ignoring().antMatchers("/auth/**");
    }

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

        // Define main authentication filter
        http.addFilterBefore(authenticationTokenProcessingFilter,
                UsernamePasswordAuthenticationFilter.class)

                // Request path authorization
                .authorizeRequests()
                .antMatchers("/api/**")
                .access("isAuthenticated()")
                .and()

                // Security failure exception handling
                .exceptionHandling()
                .authenticationEntryPoint(customAuthenticationEntryPoint)

                // Session Management
                .and().sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                // Default security HTTP headers
                .and().headers().xssProtection().frameOptions()
                .cacheControl().contentTypeOptions();
    }
}

虽然我相信我的问题已通过固定配置解决,但我仍然不知道为什么应用程序在我的 AuthenticationProvider 实现抛出异常时无限调用 authenticate()?我尝试逐步检查 Spring 安全性 classes,但我没有找到合乎逻辑的答案。在此先感谢您的专业知识!

几周前我也重现了这种行为,

处理这个问题我发现当 AuthenticationManager 在内部迭代它的关联列表 AuthenticationProviders 时会发生循环,然后找到自定义提供程序并尝试使用提供程序进行身份验证已被发现。如果提供商通过调用 authenticate() 将身份验证委托回 AuthenticationManager,您就处于循环中。我猜你的 AuthenticationProviderImpl 会做类似的事情?

您在 AuthenticationManagerjava.util.List 内的供应商中的顺序很重要。该顺序由您的配置给出,例如通过做你最初尝试的事情:

// Authentication provider
.and()
.authenticationProvider(authenticationProvider)

通过更改您的配置,您影响了与您的经理关联的内部管理的提供商列表,最终将解决您的代码。