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已经自动注册了一个默认实例。认证成功后,两个过滤器的处理逻辑冲突,且自定义过滤器未配置成功处理器,导致重定向失效。
具体修复步骤
替换默认过滤器而非添加前置过滤器
修改配置代码,用自定义过滤器替换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 }为自定义过滤器配置认证成功处理器
原代码仅设置了失败处理器,缺少成功处理器导致认证成功后无重定向逻辑。添加默认的SavedRequestAwareAuthenticationSuccessHandler,它会自动重定向到用户之前请求的页面:fun authenticationFilter() = CaptchaLoginFilter(captchaService).apply { setAuthenticationFailureHandler(SimpleUrlAuthenticationFailureHandler("/login?error=true")) // 添加认证成功处理器 setAuthenticationSuccessHandler(SavedRequestAwareAuthenticationSuccessHandler()) }确保认证流程返回值正确
确认attemptAuthentication方法在认证成功时返回super.attemptAuthentication的结果,不要在正常流程返回null,保证Spring Security能正确处理认证成功后的逻辑。
验证效果
修改后,启用captchaEnabled时,自定义过滤器会完全接管登录认证流程,验证码验证通过后执行用户名密码认证,成功后由配置的成功处理器触发重定向,解决原有的重定向失效问题。
内容的提问来源于stack exchange,提问作者Gábor Kvesdán

