Spring Boot 3.2:如何将BadCredentialsException以JSON格式返回
问题描述
我有一个使用Spring Security做认证的Spring Boot服务,通过自定义AuthenticationProvider实现凭证校验,代码如下:
@Component class CustomAuthenticationProvider(private val userService: IUserService) : AuthenticationProvider { @Throws(AuthenticationException::class, UsernameNotFoundException::class, BadCredentialsException::class) override fun authenticate(authentication: Authentication): Authentication { val username: String = authentication.principal.toString() val password: String = authentication.credentials.toString() val encoder = BCryptPasswordEncoder() val user = userService.getUser(username) ?: throw UsernameNotFoundException("User not found") if (user.accountStatus == "deactivated") throw UsernameNotFoundException("User was deleted") if (!encoder.matches(password, user.password)) { throw BadCredentialsException("1000") } return UsernamePasswordAuthenticationToken(username, password, emptyList()) } override fun supports(authentication: Class<*>): Boolean { return (authentication == UsernamePasswordAuthenticationToken::class.java) } }
当用户凭证不正确时,客户端收到的是Tomcat返回的403 HTML错误页面:
Error when logging in Error transferring https:..... - server replied: <!doctype html><html lang="en"><head><title>HTTP Status 403 – Forbidden</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 403 – Forbidden</h1><hr class="line" /><p><b>Type</b> Status Report</p><p><b>Message</b> Access Denied</p><p><b>Description</b> The server understood the request but refuses to authorize it.</p><hr class="line" /><h3>Apache Tomcat/10.1.17</h3></body></html>
我希望把异常信息封装成JSON格式返回给客户端,但常规的控制器异常处理无法覆盖AuthenticationProvider中抛出的异常,该如何解决?
解决方案
AuthenticationProvider抛出的异常属于Spring Security认证流程异常,发生在DispatcherServlet处理请求之前,所以常规的@ControllerAdvice无法捕获,需要通过Spring Security专属的异常处理机制解决:
1. 自定义AuthenticationEntryPoint
实现AuthenticationEntryPoint接口,将异常信息封装为JSON返回:
@Component class CustomAuthenticationEntryPoint : AuthenticationEntryPoint { override fun commence( request: HttpServletRequest, response: HttpServletResponse, authException: AuthenticationException ) { response.contentType = "application/json;charset=UTF-8" response.status = HttpServletResponse.SC_UNAUTHORIZED // 根据异常类型构造对应错误信息和编码 val (errorCode, errorMsg) = when (authException) { is UsernameNotFoundException -> Pair("2001", authException.message ?: "用户不存在或已删除") is BadCredentialsException -> { val code = authException.message ?: "2002" val msg = if (code == "1000") "密码错误" else "凭证无效" Pair(code, msg) } else -> Pair("2000", "认证失败") } val result = mapOf( "code" to errorCode, "message" to errorMsg, "success" to false ) val objectMapper = ObjectMapper() response.writer.write(objectMapper.writeValueAsString(result)) } }
2. 配置SecurityFilterChain
在Spring Security配置类中,将自定义的AuthenticationEntryPoint配置到HttpSecurity,替换默认异常处理逻辑:
@Configuration @EnableWebSecurity class SecurityConfig( private val customAuthenticationProvider: CustomAuthenticationProvider, private val customAuthenticationEntryPoint: CustomAuthenticationEntryPoint ) { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http .authorizeHttpRequests { auth -> auth.anyRequest().authenticated() } .formLogin { form -> // 表单登录场景下,指定登录失败后的异常处理 form.failureHandler { request, response, exception -> customAuthenticationEntryPoint.commence(request, response, exception) } } .authenticationProvider(customAuthenticationProvider) .exceptionHandling { exception -> // 配置全局认证异常处理入口 exception.authenticationEntryPoint(customAuthenticationEntryPoint) } .csrf { it.disable() } // 根据业务需求决定是否关闭CSRF return http.build() } }
补充说明
- 若使用HTTP Basic或其他认证方式,需在对应配置中指定
failureHandler,或通过exceptionHandling全局配置authenticationEntryPoint。 - 可根据业务需求扩展异常类型判断,返回更精准的错误信息。
- 确保项目已引入Jackson依赖(Spring Boot Web Starter默认包含),用于JSON序列化。
内容的提问来源于stack exchange,提问作者Cristi
相关产品推荐
相关产品推荐

