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

Spring Authorization Server添加验证码过滤器后授权码模式不跳转

登录验证码过滤器认证成功后无法重定向的问题排查与解决思路

问题现象

为登录功能添加验证码保护,在认证流程中配置了自定义过滤器。日志显示验证码验证和认证均成功,但认证完成后无法重定向回客户端。禁用captchaEnabled开关或注释掉http.addFilterBefore代码行时,功能恢复正常。

相关配置代码

@Bean
@Order(2)
fun defaultSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
    val authFilter = authenticationFilter()
    if (captchaEnabled) {
        http.addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter::class.java)
    }

    val filterChain =
        http.authorizeHttpRequests { authorize ->
            authorize
                .requestMatchers(
                    "/api/v1/auth-service/.well-known/jwks.json",
                    "/error",
                    "/login",
                    "/images/**",
                    "/css/**",
                    "/js/**",
                    "/templates/**",
                    "favicon.ico",
                ).permitAll()
                .anyRequest().authenticated()
        }
            .formLogin { it.loginPage("/login").permitAll() }
            .cors { it.configurationSource(corsConfigurationSource()) }.build()

    if (captchaEnabled) {
        val authManager = http.getSharedObject(AuthenticationManager::class.java)
        authFilter.setAuthenticationManager(authManager)
    }
    return filterChain
}

fun authenticationFilter() =
    CaptchaLoginFilter(captchaService).apply {
        setAuthenticationFailureHandler(SimpleUrlAuthenticationFailureHandler("/login?error=true"))
    }

过滤器实现代码

class CaptchaLoginFilter(
    private val captchaService: CaptchaService?,
) : UsernamePasswordAuthenticationFilter() {
    override fun attemptAuthentication(
        request: HttpServletRequest,
        response: HttpServletResponse,
    ): Authentication? {
        if (!request.method.equals("POST")) {
            throw AuthenticationServiceException("Authentication method not supported: " + request.method)
        }

        val recaptchaFormResponse = request.getParameter("g-recaptcha-response")

        try {
            captchaService?.processResponse(
                recaptchaFormResponse,
                request.remoteAddr,
                null,
                request.getHeader("User-Agent"),
            )
        } catch (e: Exception) {
            when (e) {
                is RecaptchaException -> {
                    // TODO
                    // request.getRequestDispatcher("otp_login").forward(request, response)
                }

                else ->
                    try {
                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.message)
                    } catch (ioException: IOException) {
                        ioException.printStackTrace()
                    }
            }
            return null
        }
        try {
            return super.attemptAuthentication(request, response)
        } catch (e: Exception) {
            when (e) {
                is AuthenticationException -> {
                    throw e
                }

                else -> {
                    logger.warn("Unexpected exception during authentication", e)
                    response.sendRedirect("/login?error")
                    return null
                }
            }
        }
    }
}

解决思路与方案

核心问题分析

问题根源在于同时存在两个UsernamePasswordAuthenticationFilter实例:自定义的CaptchaLoginFilter继承自该类,通过addFilterBefore添加到默认过滤器前,但Spring Security的formLogin已经自动注册了一个默认实例。认证成功后,两个过滤器的处理逻辑冲突,且自定义过滤器未配置成功处理器,导致重定向失效。

具体修复步骤

  1. 替换默认过滤器而非添加前置过滤器
    修改配置代码,用自定义过滤器替换formLogin默认的UsernamePasswordAuthenticationFilter,避免重复实例冲突:

    @Bean
    @Order(2)
    fun defaultSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
        val filterChain = http.authorizeHttpRequests { authorize ->
                authorize
                    .requestMatchers(
                        "/api/v1/auth-service/.well-known/jwks.json",
                        "/error",
                        "/login",
                        "/images/**",
                        "/css/**",
                        "/js/**",
                        "/templates/**",
                        "favicon.ico",
                    ).permitAll()
                    .anyRequest().authenticated()
            }
            .formLogin { form ->
                form.loginPage("/login").permitAll()
                // 替换默认过滤器为自定义验证码过滤器
                if (captchaEnabled) {
                    val authFilter = authenticationFilter()
                    val authManager = http.getSharedObject(AuthenticationManager::class.java)
                    authFilter.setAuthenticationManager(authManager)
                    form.authenticationFilter(authFilter)
                }
            }
            .cors { it.configurationSource(corsConfigurationSource()) }
            .build()
    
        return filterChain
    }
    
  2. 为自定义过滤器配置认证成功处理器
    原代码仅设置了失败处理器,缺少成功处理器导致认证成功后无重定向逻辑。添加默认的SavedRequestAwareAuthenticationSuccessHandler,它会自动重定向到用户之前请求的页面:

    fun authenticationFilter() =
        CaptchaLoginFilter(captchaService).apply {
            setAuthenticationFailureHandler(SimpleUrlAuthenticationFailureHandler("/login?error=true"))
            // 添加认证成功处理器
            setAuthenticationSuccessHandler(SavedRequestAwareAuthenticationSuccessHandler())
        }
    
  3. 确保认证流程返回值正确
    确认attemptAuthentication方法在认证成功时返回super.attemptAuthentication的结果,不要在正常流程返回null,保证Spring Security能正确处理认证成功后的逻辑。

验证效果

修改后,启用captchaEnabled时,自定义过滤器会完全接管登录认证流程,验证码验证通过后执行用户名密码认证,成功后由配置的成功处理器触发重定向,解决原有的重定向失效问题。

内容的提问来源于stack exchange,提问作者Gábor Kvesdán

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 15:33:16