Spring Boot 3.1.2与Spring Security自定义401 Unauthorized响应失效问题排查
嗨,我来帮你捋捋这个问题——你遇到的核心问题是OAuth2资源服务器的认证失败场景没被全局异常处理配置覆盖,导致自定义401响应没生效,反而返回了空白body。
问题根源
在Spring Security的OAuth2资源服务器模式下,oauth2ResourceServer()配置块本身自带默认的AuthenticationEntryPoint(比如BearerTokenAuthenticationEntryPoint),专门处理JWT令牌相关的认证失败(比如令牌缺失、无效、过期等)。当你把自定义的CustomAuthenticationHandler放在全局的exceptionHandling()里时,这个全局配置并不会覆盖OAuth2资源服务器专属的认证入口配置,所以触发401时,还是默认的entryPoint在工作——而它通常只设置401状态码,不会返回任何响应体,这就是空白body的由来。
解决方案
给你两个可行的修复方案,按需选择:
方案1:把自定义EntryPoint配置到OAuth2资源服务器内部
这和你最初的有效写法逻辑一致,用lambda风格优化后更简洁:
@Autowired private CustomAuthenticationHandler customAuthenticationHandler; // 注入Bean,不要手动new(如果有依赖注入需求的话) @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .cors(AbstractHttpConfigurer::disable) .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(authorize -> authorize .anyRequest().authenticated()) .sessionManagement(smc -> smc .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 重点:将自定义EntryPoint配置到oauth2ResourceServer内部 .oauth2ResourceServer(oauth2 -> oauth2 .jwt(Customizer.withDefaults()) .authenticationEntryPoint(customAuthenticationHandler)) // 若存在非OAuth2的认证场景(比如表单登录),可保留全局异常处理 .exceptionHandling(exception -> exception .authenticationEntryPoint(customAuthenticationHandler)); return http.build(); }
方案2:确保自定义EntryPoint逻辑正确实现
如果你坚持用全局异常处理,先检查CustomAuthenticationHandler是否正确实现了AuthenticationEntryPoint接口,尤其要保证响应体被正确写入:
public class CustomAuthenticationHandler implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { // 设置401状态码 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 指定响应内容类型 response.setContentType("application/json;charset=UTF-8"); // 写入自定义响应体(可根据业务需求调整内容) String responseBody = "{\"code\":401,\"message\":\"认证失败,请提供有效的令牌\",\"data\":null}"; response.getWriter().write(responseBody); response.getWriter().flush(); } }
另外要注意:如果CustomAuthenticationHandler依赖其他Spring Bean(比如ObjectMapper序列化JSON),绝对不能手动new这个类,必须通过@Autowired注入,否则依赖会失效,可能导致写入响应时出错(比如序列化失败,最终返回空白)。
为什么最初的写法有效?
你最初的代码直接把注入的authenticationEntryPoint设置到oauth2ResourceServer()的配置里,相当于替换了OAuth2资源服务器的默认认证入口,所以令牌相关的认证失败会触发你的自定义逻辑,返回预期的响应体。
备注:内容来源于stack exchange,提问作者James

