You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security ProviderManager循环调用及异常处理问题求助

Spring Security 6.2.3 认证异常无限循环与处理问题

问题背景

使用org.springframework.security:spring-security-core:6.2.3版本,自定义了TokenVerifyFilter过滤器:

class TokenVerifyFilter(
    private val authenticationManager: AuthenticationManager,
    private val authenticationEntryPoint: SecurityAuthenticationEntryPoint
) : OncePerRequestFilter() {
    override fun doFilterInternal(
        request: HttpServletRequest,
        response: HttpServletResponse,
        filterChain: FilterChain
    ) {
        // ...
        val authenticate = authenticationManager.authenticate(BearerTokenAuthenticationToken(token))
        // ...
    }
}

同时注册了两个AuthenticationProvider:

@Bean
fun authenticationManager(
    http: HttpSecurity,
    loginAuthenticationProvider: LoginAuthenticationProvider,
    bearerTokenAuthenticationProvider: BearerTokenAuthenticationProvider
): AuthenticationManager {
    val authenticationManagerBuilder =
        http.getSharedObject(AuthenticationManagerBuilder::class.java)
    authenticationManagerBuilder.authenticationProvider(loginAuthenticationProvider).authenticationProvider(bearerTokenAuthenticationProvider)
    return authenticationManagerBuilder.build()
}

当BearerTokenAuthenticationProvider#authenticate抛出继承自AuthenticationException的异常时,系统陷入无限循环。排查发现问题出在ProviderManager的逻辑中:

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // ...
        for (AuthenticationProvider provider : getProviders()) {
            // ...
            try {
                result = provider.authenticate(authentication);
                if (result != null) {
                    copyDetails(authentication, result);
                    break;
                }
            }
            // 1. catch AuthenticationException
            catch (AuthenticationException ex) {
                lastException = ex;
            }
        }
        if (result == null && this.parent != null) {
            // Allow the parent to try.
            try {
                // 2. Trapped in a loop here, Return to the first step.
                parentResult = this.parent.authenticate(authentication);
                result = parentResult;
            }
            // ...
        }
    }

}

手动设置authenticationManagerBuilder.parentAuthenticationManager(null)解决了循环问题,但此时抛出的异常会被DispatcherServlet捕获,而非进入后续过滤器处理。现需解决两个问题:


问题解答

1. 未设置父ProviderManager,为何父实例与当前实例相同?

当你通过http.getSharedObject(AuthenticationManagerBuilder::class.java)获取的构建器,默认会关联Spring Security的全局AuthenticationManager作为父容器。而你自定义的AuthenticationManager Bean会被Spring Security自动识别为全局认证管理器,导致构建器在创建实例时,将当前自定义的AuthenticationManager设置为自身的父级,形成循环引用。最终调用parent.authenticate()时,实际还是调用了当前实例的认证逻辑,引发无限循环。

2. 如何让异常正常进入后续过滤器,而非被DispatcherServlet捕获?

核心是不要让认证异常直接逃逸到DispatcherServlet,而是交给Spring Security的异常处理机制处理,同时确保AuthenticationManager无循环引用:

步骤1:修正AuthenticationManager配置,明确设置父级为null

@Bean
fun authenticationManager(
    http: HttpSecurity,
    loginAuthenticationProvider: LoginAuthenticationProvider,
    bearerTokenAuthenticationProvider: BearerTokenAuthenticationProvider
): AuthenticationManager {
    val authenticationManagerBuilder =
        http.getSharedObject(AuthenticationManagerBuilder::class.java)
    authenticationManagerBuilder
        .authenticationProvider(loginAuthenticationProvider)
        .authenticationProvider(bearerTokenAuthenticationProvider)
        .parentAuthenticationManager(null) // 显式清除父级,避免循环
    return authenticationManagerBuilder.build()
}

步骤2:修改TokenVerifyFilter,捕获认证异常并交给AuthenticationEntryPoint处理

在过滤器内部捕获AuthenticationException,调用配置好的AuthenticationEntryPoint来处理异常,这样会触发Spring Security的标准异常处理流程,而非让异常直接抛出到DispatcherServlet:

class TokenVerifyFilter(
    private val authenticationManager: AuthenticationManager,
    private val authenticationEntryPoint: SecurityAuthenticationEntryPoint
) : OncePerRequestFilter() {
    override fun doFilterInternal(
        request: HttpServletRequest,
        response: HttpServletResponse,
        filterChain: FilterChain
    ) {
        try {
            // 获取token的逻辑
            val token = // ... 从request中提取token
            val authentication = authenticationManager.authenticate(BearerTokenAuthenticationToken(token))
            // 将认证结果存入SecurityContext
            SecurityContextHolder.getContext().authentication = authentication
            // 继续执行后续过滤器链
            filterChain.doFilter(request, response)
        } catch (ex: AuthenticationException) {
            // 调用AuthenticationEntryPoint处理认证失败,而非直接抛出异常
            authenticationEntryPoint.commence(request, response, ex)
        }
    }
}

这种方式既避免了无限循环,又能让异常通过Spring Security的机制处理,不会被DispatcherServlet提前捕获。


内容的提问来源于stack exchange,提问作者SageJustus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 04:36:10