
在 Spring Boot + JWT 的 REST 应用中,当 JWT 过期或非法时,过滤器内抛出的异常默认不会被全局异常处理器捕获,需通过 @ResponseStatus 注解声明 HTTP 状态与错误信息,才能确保浏览器收到标准化的 JSON 错误响应。
在 spring boot + jwt 的 rest 应用中,当 jwt 过期或非法时,过滤器内抛出的异常默认不会被全局异常处理器捕获,需通过 `@responsestatus` 注解声明 http 状态与错误信息,才能确保浏览器收到标准化的 json 错误响应。
在基于 Spring Security 的 JWT 认证流程中,JwtTokenFilter 作为 OncePerRequestFilter 在请求进入 Controller 前执行鉴权逻辑。此时若 token 已过期或签名无效,JwtTokenProvider.validateToken() 中抛出的 JwtAuthenticationException 不会自动触发 @ControllerAdvice 或 @ExceptionHandler —— 因为异常发生在 DispatcherServlet 的请求分发之前,全局异常处理器尚未生效。
因此,最简洁、标准且符合 REST 规范的解决方案是:为自定义异常添加 @ResponseStatus 注解,让 Spring MVC 在捕获该异常时自动设置 HTTP 状态码和响应头,并交由默认错误机制(如 BasicErrorController)生成响应体。
✅ 正确做法如下:
- 定义带状态码的自定义异常类(推荐放在
exception包下):
@ResponseStatus(value = HttpStatus.UNAUTHORIZED, reason = "JWT token is expired or invalid!")
public class JwtAuthenticationException extends RuntimeException {
public JwtAuthenticationException(String message) {
super(message);
}
}⚠️ 注意:
HttpStatus.UNAUTHORIZED (401)比FORBIDDEN (403)更语义准确——token 失效属于认证失败(Authentication),而非权限不足(Authorization)。若业务要求区分“未登录”与“登录失效”,也可统一用401并在响应体中明确提示。立即学习“前端免费学习笔记(深入)”;
- 在过滤器中主动捕获并抛出该异常(关键修改点):
更新 JwtTokenFilter.doFilterInternal(),包裹 jwtTokenProvider.validateToken(token) 及其依赖调用(如 getAuthentication())于 try-catch 中,并显式抛出 JwtAuthenticationException:
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = jwtTokenProvider.resolveToken(request);
if (token != null) {
try {
if (jwtTokenProvider.validateToken(token)) {
Authentication authentication = jwtTokenProvider.getAuthentication(token);
if (authentication != null) {
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} else {
throw new JwtAuthenticationException("JWT token is expired or invalid!");
}
} catch (JwtAuthenticationException e) {
throw e; // 直接抛出,由 Spring 自动处理
} catch (Exception e) {
// 捕获其他 JWT 解析异常(如签名错误、格式错误等)
throw new JwtAuthenticationException("JWT token is expired or invalid!");
}
}
filterChain.doFilter(request, response);
}✅ 为什么
throw e即可?因为@ResponseStatus已声明该异常应映射为401响应;Spring MVC 的DispatcherServlet会在整个请求生命周期(包括 Filter 链)中识别并处理此类注解异常,最终调用DefaultHandlerExceptionResolver生成标准错误响应(如{"timestamp":"...","status":401,"error":"Unauthorized","message":"JWT token is expired or invalid!","path":"/api/xxx"})。
-
(可选但推荐)补充全局异常处理器,统一响应格式:
若项目已使用@ControllerAdvice统一返回 JSON 结构(如{ "code": 401, "message": "...", "data": null }),可额外添加对JwtAuthenticationException的处理:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(JwtAuthenticationException.class)
public ResponseEntity<ApiResponse<?>> handleJwtException(JwtAuthenticationException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(ApiResponse.error(401, e.getMessage()));
}
}此时需移除 @ResponseStatus(二者不共存),由该处理器完全接管响应逻辑。
? 重要注意事项:
- 不要在
doFilterInternal中直接调用response.sendError()或手动写入响应体(如response.getWriter().write(...)),否则会破坏 Spring 的响应生命周期,导致后续 Filter/Interceptor/Controller 无法执行,且可能引发IllegalStateException: getWriter() has already been called。 - 确保
JwtAuthenticationException类不在@Component或@Service等 Spring 托管范围内,它只是一个 POJO 异常类型,无需注册为 Bean。 - 若使用 Spring Boot 2.3+,默认启用
server.error.include-message=never(出于安全考虑隐藏错误详情),如需返回reason字段,可在application.yml中配置:server: error: include-message: always
通过以上改造,当用户携带过期 JWT 访问受保护接口时,浏览器将收到清晰、标准化的 401 Unauthorized 响应及可读错误消息,前端可据此跳转登录页或刷新 Token,真正实现异常从 Filter 到用户的端到端透传。


















