Java Spring项目JWT认证自定义异常不生效问题
问题根因
@ControllerAdvice 全局异常处理器的生效范围是Spring MVC的DispatcherServlet处理链路,仅能捕获Controller层抛出、以及Controller之前MVC链路内传播的异常。你的OnlineStoreAPIException是在Spring Security过滤器链的JwtAuthenticationFilter.doFilterInternal方法中抛出的,过滤器执行时机早于DispatcherServlet,异常不会传递到全局异常处理器,最终会被Servlet容器默认的异常处理逻辑拦截,返回带完整栈信息的500错误。
解决方案
优先选择在过滤器内部直接处理异常的方案,实现简单、无额外转发逻辑,是JWT认证场景下的通用实践:
- 在
JwtAuthenticationFilter的doFilterInternal方法中,用try-catch块包裹所有JWT校验、认证逻辑,直接捕获OnlineStoreAPIException,手动构造符合格式的响应写入返回流,不再向上抛出异常。
参考实现代码:
import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.core.Authentication; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; import java.time.Instant; public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; private final ObjectMapper objectMapper; // 构造方法注入依赖 public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider, ObjectMapper objectMapper) { this.jwtTokenProvider = jwtTokenProvider; this.objectMapper = objectMapper; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { String token = extractTokenFromRequest(request); if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) { Authentication authentication = jwtTokenProvider.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(authentication); } filterChain.doFilter(request, response); } catch (OnlineStoreAPIException e) { // 重置响应状态、内容类型 response.setStatus(e.getHttpStatus().value()); response.setContentType("application/json;charset=UTF-8"); // 构造和预期格式一致的错误对象 ErrorDetails error = new ErrorDetails( Instant.now().toString(), e.getHttpStatus().name(), e.getMessage() ); // 将错误对象序列化为JSON写入响应 response.getWriter().write(objectMapper.writeValueAsString(error)); } } private String extractTokenFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } }
- 注意事项:
- 捕获异常后不要继续调用
filterChain.doFilter(),避免后续过滤器执行触发额外异常 - 项目中如果已经全局配置了ObjectMapper(比如处理了Java时间序列化),直接注入使用即可,不需要手动新建实例
- 确保
ErrorDetails类的字段名、序列化规则和你预期的响应格式匹配
- 捕获异常后不要继续调用
如果希望所有异常统一走全局异常处理器处理,也可以选择异常转发方案:
- 在过滤器中捕获到
OnlineStoreAPIException后,将异常对象存入request的attribute中,转发到项目内的错误路径(比如/filter-error) - 在Controller层新增对应
/filter-error的接口,从request中取出异常对象直接抛出 - 此时异常会进入
DispatcherServlet处理链路,被@ControllerAdvice正常捕获
该方案需要额外配置转发规则,避免出现循环转发问题,适合需要统一所有异常处理逻辑的场景。
内容的提问来源于stack exchange,提问作者Andres Ramirez
相关产品推荐
相关产品推荐

