Spring Security仅返回401 Unauthorized问题排查诉求
我开发了一个基于Spring Boot 3的REST API,用Spring Security + JWT做认证。现在遇到个问题:触发自定义异常(比如RecipeNotFoundException应该返回404)或者服务器内部错误时,始终返回401 Unauthorized,而不是对应的状态码。
相关代码实现
1. SecurityFilterChain 配置
@Bean protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // No need for CSRF in this example http .cors() .and() .csrf().disable() // No authentication .authorizeHttpRequests() .requestMatchers("/login", "/register").permitAll() // Authentication for other requests .anyRequest().authenticated() .and() // Session is stateless .exceptionHandling() .authenticationEntryPoint(jwtAuthenticationEntryPoint) .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); }
2. JwtAuthenticationEntryPoint 实现
@Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); } }
3. 自定义异常类及业务使用
@ResponseStatus(value = HttpStatus.NOT_FOUND) public class RecipeNotFoundException extends RuntimeException { }
业务逻辑中触发异常:
if (recipeFound.isEmpty()) throw new RecipeNotFoundException();
4. JwtRequestFilter 实现
@Component public class JwtRequestFilter extends OncePerRequestFilter { private final JwtUserDetailsService jwtUserDetailsService; private final JwtTokenUtil jwtTokenUtil; public JwtRequestFilter(JwtUserDetailsService jwtUserDetailsService, JwtTokenUtil jwtTokenUtil) { this.jwtUserDetailsService = jwtUserDetailsService; this.jwtTokenUtil = jwtTokenUtil; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { if (!request.getRequestURI().equals("/register") && !request.getRequestURI().equals("/login")) { final String requestTokenHeader = request.getHeader("Authorization"); String username = null; String jwtToken = null; // Remove Bearer word from token if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) { jwtToken = requestTokenHeader.substring(7); try { username = jwtTokenUtil.getUsernameFromToken(jwtToken); } catch (IllegalArgumentException e) { System.out.println("Unable to get JWT Token"); } catch (ExpiredJwtException e) { System.out.println("JWT Token has expired"); } } else { logger.warn("JWT Token does not start with Bearer String"); } // Validate token if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username); // Configure authentication in Spring Security if (jwtTokenUtil.validateToken(jwtToken, userDetails)) { UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); // Successful Authentication SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); } } } chain.doFilter(request, response); } }
问题原因分析
异常处理覆盖问题:你只配置了
AuthenticationEntryPoint,它仅处理认证相关异常(比如JWT无效、未携带Token),但自定义业务异常、服务器内部错误不属于认证异常。不过因为你设置了.anyRequest().authenticated(),如果JWT过滤器没有在SecurityContext中设置合法的Authentication,Spring Security会直接判定请求未认证,触发AuthenticationEntryPoint返回401,覆盖后续的异常响应。JWT过滤器逻辑缺陷:当JWT无效/过期时,你仅打印日志但未明确触发认证失败,也没有终止请求流程。此时SecurityContext为空,后续走到授权环节时,Spring Security会直接返回401,导致业务异常根本没机会被处理。
解决方案
步骤1:添加全局异常处理器
创建全局异常处理类,专门捕获业务异常和通用异常:
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(RecipeNotFoundException.class) public ResponseEntity<String> handleRecipeNotFound(RecipeNotFoundException ex) { return new ResponseEntity<>("食谱不存在", HttpStatus.NOT_FOUND); } @ExceptionHandler(Exception.class) public ResponseEntity<String> handleGeneralException(Exception ex) { return new ResponseEntity<>("服务器内部错误", HttpStatus.INTERNAL_SERVER_ERROR); } }
步骤2:修复JWT过滤器的认证失败逻辑
在JWT无效/过期时,抛出Spring Security标准认证异常,明确触发认证失败流程:
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { if (!request.getRequestURI().equals("/register") && !request.getRequestURI().equals("/login")) { final String requestTokenHeader = request.getHeader("Authorization"); String username = null; String jwtToken = null; // 处理Token格式 if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) { jwtToken = requestTokenHeader.substring(7); try { username = jwtTokenUtil.getUsernameFromToken(jwtToken); } catch (IllegalArgumentException e) { logger.error("无法解析JWT Token", e); throw new BadCredentialsException("无效的JWT Token"); } catch (ExpiredJwtException e) { logger.error("JWT Token已过期", e); throw new CredentialsExpiredException("JWT Token已过期"); } } else if (requestTokenHeader != null) { logger.warn("JWT Token格式错误,未以Bearer开头"); throw new BadCredentialsException("JWT Token格式无效"); } else { // 未携带Token,直接放行到授权环节触发401 chain.doFilter(request, response); return; } // 验证Token并设置认证信息 if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username); if (!jwtTokenUtil.validateToken(jwtToken, userDetails)) { throw new BadCredentialsException("无效的JWT Token"); } UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); } } chain.doFilter(request, response); }
步骤3:完善SecurityFilterChain的异常处理
添加accessDeniedHandler处理授权失败场景,确保认证通过后的异常能被全局处理器捕获:
@Bean protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .cors().and() .csrf().disable() .authorizeHttpRequests(auth -> auth .requestMatchers("/login", "/register").permitAll() .anyRequest().authenticated() ) .exceptionHandling(exceptions -> exceptions .authenticationEntryPoint(jwtAuthenticationEntryPoint) .accessDeniedHandler((request, response, ex) -> { response.sendError(HttpServletResponse.SC_FORBIDDEN, "权限不足"); }) ) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); }
关键说明
- 全局异常处理器负责处理认证通过后的业务异常和通用错误,返回对应状态码。
- JWT过滤器在认证失败时抛出标准异常,由
AuthenticationEntryPoint处理返回401。 - 只有当请求成功通过认证(SecurityContext中有合法Authentication),业务逻辑抛出的异常才会被全局处理器捕获,返回正确的响应。
内容的提问来源于stack exchange,提问作者30james00
相关产品推荐
相关产品推荐

