在 spring boot 中捕获令牌过期异常,核心做法是自定义一个全局异常处理器,通过 @controlleradvice 和 @exceptionhandler 拦截特定的异常类(如 expiredjwtexception),并返回统一的 json 错误响应。具体实现时,需确保该处理器的优先级高于框架默认的异常处理机制,通常通过实现 handlerexceptionresolver 接口或提高 @restcontrolleradvice 的排序值来完成,从而保证前端能收到标准的 401 状态码及包含错误信息的 body。

如何配置全局异常处理器以优先捕获 JWT 异常
要实现这一功能,首先需要定义一个专门处理 JWT 异常的 Bean。由于 Spring Security 或过滤器链可能在 Controller 层之前抛出异常,直接依赖 @RestControllerAdvice 有时无法拦截到 Filter 层的异常。因此,推荐实现 HandlerExceptionResolver 接口,并将其注册为 Bean,同时设置较高的 Order 值以确保其优先执行。这样可以在异常到达 DispatcherServlet 之前进行拦截。

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import io.jsonwebtoken.ExpiredJwtException;
import java.util.HashMap;
import java.util.Map;
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class JwtGlobalExceptionHandler implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response,
Object handler, Exception ex) {
Map<String, Object> body = new HashMap<>();
HttpStatus status;
if (ex instanceof ExpiredJwtException) {
status = HttpStatus.UNAUTHORIZED;
body.put("error", "Token expired");
body.put("message", "Authentication token has expired");
} else if (ex instanceof SecurityException || ex instanceof AuthenticationException) {
status = HttpStatus.UNAUTHORIZED;
body.put("error", "Invalid token");
body.put("message", "Token is invalid or not found");
} else {
// 其他未处理异常
status = HttpStatus.INTERNAL_SERVER_ERROR;
body.put("error", "Internal Server Error");
body.put("message", "An unexpected error occurred");
}
response.setStatus(status.value());
response.setContentType("application/json;charset=UTF-8");
try {
response.getWriter().write(new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(body));
} catch (java.io.IOException e) {
e.printStackTrace();
}
return new ModelAndView();
}
}
上述代码中,@Order(Ordered.HIGHEST_PRECEDENCE) 确保了该解析器在所有其他解析器之前运行。当 ExpiredJwtException 发生时,它会被立即捕获,响应状态码被设置为 401,并通过 response.getWriter() 写入 JSON 内容。这种方式绕过了视图解析器,直接输出数据,非常适合 RESTful API 场景。
如何在统一响应结构中封装令牌过期的错误信息
为了保持前后端交互的一致性,通常需要将错误信息封装在一个统一的响应结构中,而不是直接返回裸 Map。我们可以定义一个通用的 Result 类,包含状态码、消息和数据字段。在全局异常处理器中,将具体的异常信息映射到这个结构体中,确保前端可以通过固定的字段路径获取错误详情。此外,还需要注意日志记录,以便后端开发人员排查问题。
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import io.jsonwebtoken.ExpiredJwtException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
// 假设有一个统一的 Result 类
// public class Result { private int code; private String message; private Object data; ... }
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class UnifiedJwtExceptionHandler implements HandlerExceptionResolver {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
Map<String, Object> result = new HashMap<>();
int statusCode;
String errorCode;
String errorMessage;
if (ex instanceof ExpiredJwtException) {
statusCode = 401;
errorCode = "TOKEN_EXPIRED";
errorMessage = "Login session expired, please re-login";
} else if (ex instanceof io.jsonwebtoken.MalformedJwtException) {
statusCode = 401;
errorCode = "INVALID_TOKEN";
errorMessage = "Malformed authentication token";
} else {
statusCode = 500;
errorCode = "SYSTEM_ERROR";
errorMessage = "Internal server error";
}
result.put("code", statusCode);
result.put("msg", errorMessage);
result.put("data", null);
response.setStatus(statusCode);
response.setContentType("application/json;charset=UTF-8");
try {
response.getWriter().write(objectMapper.writeValueAsString(result));
} catch (Exception e) {
e.printStackTrace();
}
return new ModelAndView();
}
}
在这个改进的版本中,我们引入了更明确的错误码(如 TOKEN_EXPIRED)和友好的提示信息。前端可以根据 code 字段判断是否需要跳转登录页,根据 msg 字段展示用户提示。这种结构化的响应方式提高了系统的可维护性和用户体验。objectMapper 用于将 Map 序列化为 JSON 字符串,确保输出的格式符合标准。
当令牌过期时,前端应如何处理重定向或刷新逻辑
虽然后端负责抛出异常并返回 401,但前端的处理逻辑同样关键。当检测到令牌过期时,前端不应仅仅显示错误,而应自动清除本地存储的无效 Token,并重定向到登录页面。或者,如果应用支持无感刷新,前端可以尝试使用 Refresh Token 获取新的 Access Token。在本例中,我们关注的是后端如何配合前端完成这一流程。后端返回的标准 JSON 结构使得前端可以轻松地解析并执行相应操作。例如,Axios 拦截器可以监听 401 状态码,检查返回的 code 是否为 TOKEN_EXPIRED,然后触发登出逻辑。


















