如何处理JWT SignatureException,返回400而非500状态码?
兄弟,你这个判断完全正确!返回500确实不合理,400才是更贴合无效token场景的状态码,甚至用401 Unauthorized会更符合HTTP语义(后面会说),先给你理清楚前因后果,再给你解决办法:
先唠状态码的合理性
500是服务器内部错误,意思是服务器自己处理请求时出了意外,跟客户端没关系;但无效token明显是客户端提交的凭证/参数不合法,属于客户端的问题,用400(Bad Request)完全合理。如果是认证失败的场景,其实401(未授权)更精准——它表示请求需要合法的认证凭证,无效token正好符合这个场景,不过如果你业务上就想定400,也完全没问题。
为啥抛了ResponseStatusException还是返回500?
十有八九是因为你抛出异常的时机不对!比如你在**过滤器(Filter)或者拦截器(Interceptor)**里做token校验并抛出异常,这时候Spring MVC的DispatcherServlet还没接管请求,它的异常处理机制(包括ResponseStatusException的自动处理)根本没生效,这些异常会被Tomcat这类容器直接捕获,返回默认的500错误。
正确返回400的解决方案
分两种常见场景给你说具体做法:
场景1:自己写的Token校验逻辑(没用到Spring Security)
方法一:全局异常捕获
写一个全局异常处理器,把SignatureException抓过来,手动返回400:@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(SignatureException.class) public ResponseEntity<ErrorResponse> handleInvalidToken(SignatureException ex) { ErrorResponse error = new ErrorResponse("无效的Token", ex.getMessage()); return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); } // 自定义错误响应类 static class ErrorResponse { private String message; private String detail; public ErrorResponse(String message, String detail) { this.message = message; this.detail = detail; } // getter方法 public String getMessage() { return message; } public String getDetail() { return detail; } } }只要是在Spring MVC的处理链里抛出的
SignatureException,都会被这个处理器接住,直接返回400和你自定义的响应体。方法二:过滤器里手动写响应
如果你的token校验是在Filter里做的,这时候别用ResponseStatusException,直接操作HttpServletResponse就行:public class TokenCheckFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = request.getHeader("Authorization"); try { // 你的token校验逻辑,比如解析JWT签名 validateToken(token); filterChain.doFilter(request, response); } catch (SignatureException ex) { // 手动设置状态码和响应体 response.setStatus(HttpStatus.BAD_REQUEST.value()); response.setContentType("application/json"); ObjectMapper mapper = new ObjectMapper(); ErrorResponse error = new ErrorResponse("无效的Token", ex.getMessage()); response.getWriter().write(mapper.writeValueAsString(error)); } } // 自定义的token校验方法和ErrorResponse类同上 }
场景2:用Spring Security做JWT认证
如果是Spring Security的JWT校验失败,你可以自定义一个AuthenticationEntryPoint来处理:
@Component public class CustomAuthEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { response.setStatus(HttpStatus.BAD_REQUEST.value()); response.setContentType("application/json"); ErrorResponse error = new ErrorResponse("无效的Token", authException.getMessage()); ObjectMapper mapper = new ObjectMapper(); response.getWriter().write(mapper.writeValueAsString(error)); } }
然后在Security配置里把这个EntryPoint加上:
@Configuration @EnableWebSecurity public class SecurityConfig { @Autowired private CustomAuthEntryPoint customAuthEntryPoint; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .exceptionHandling() .authenticationEntryPoint(customAuthEntryPoint) // 配置自定义异常处理 .and() // 其他配置:比如添加JWT过滤器、设置授权规则等 .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()); return http.build(); } }
这样JWT签名验证失败时,就会触发这个EntryPoint,直接返回400状态码。
额外提一句
如果业务场景是“用户未提供有效认证凭证”,用401 Unauthorized会更符合HTTP的标准语义,你只需要把上面代码里的HttpStatus.BAD_REQUEST换成HttpStatus.UNAUTHORIZED就行。
内容的提问来源于stack exchange,提问作者fr3ddie

