拦截器与 JWT 联合使用详解
1. 核心概念
1.1 什么是 JWT?
JWT 是一个开放标准(RFC 7519),用于在各方之间以 JSON 对象的形式安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
JWT 结构:
-
Header(头部):包含令牌类型(JWT)和签名算法(如 HS256、RS256)。
-
Payload(负载):包含声明(claims),如用户 ID、用户名、过期时间等。
-
Signature(签名):对前两部分进行签名,防止数据被篡改。
典型场景:用户登录成功后,服务器生成一个 JWT 返回给客户端。客户端在后续请求的 Authorization 头中携带该令牌,服务器验证令牌的合法性。
1.2 什么是拦截器?
在 Spring MVC 中,拦截器(HandlerInterceptor)用于在请求处理前后执行通用逻辑,如:
-
日志记录
-
权限校验
-
请求参数预处理
拦截器与 Servlet 的 Filter 类似,但更贴近 Spring MVC 的 Handler 执行流程。
2. 联合使用的工作流程
-
用户登录:客户端发送用户名/密码,服务器验证通过后生成 JWT 并返回。
-
客户端存储 JWT:通常存储在
localStorage或HttpOnly Cookie中。 -
携带 JWT 请求:客户端在后续请求的
Authorization头中添加Bearer <JWT>。 -
拦截器拦截请求:对于需要认证的接口,拦截器从请求头中提取 JWT,验证其有效性。
-
验证通过:将用户信息(如用户 ID)存入
ThreadLocal或请求属性,供后续业务逻辑使用。 -
验证失败:返回 401 Unauthorized 或 403 Forbidden。
3. 代码实现(Spring Boot 示例)
3.1 引入依赖
xml
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
3.2 JWT 工具类
提供生成、解析、验证 JWT 的方法。
java
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
private Key getSigningKey() {
return Keys.hmacShaKeyFor(secret.getBytes());
}
// 生成 JWT
public String generateToken(String username) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration);
return Jwts.builder()
.setSubject(username)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
// 从 JWT 中获取用户名
public String getUsernameFromToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
}
// 验证 JWT 是否有效
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
// 可以根据异常类型返回更具体的错误信息
return false;
}
}
}
3.3 自定义拦截器
实现 HandlerInterceptor,在 preHandle 方法中完成 JWT 的提取和验证。
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从请求头中获取 Authorization 字段
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
if (jwtUtil.validateToken(token)) {
// 将用户信息存入请求属性,供后续 Controller 使用
String username = jwtUtil.getUsernameFromToken(token);
request.setAttribute("username", username);
return true;
}
}
// 验证失败,返回 401 状态码
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Unauthorized: Invalid or missing token");
return false;
}
}
3.4 配置拦截器路径
通过实现 WebMvcConfigurer 来注册拦截器,并指定需要拦截的路径。
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private JwtInterceptor jwtInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/api/**") // 拦截所有 /api/** 路径
.excludePathPatterns("/api/login", "/api/register"); // 排除登录注册接口
}
}
3.5 Controller 中使用用户信息
在 Controller 中,可以通过 @RequestAttribute 获取拦截器中存入的用户信息。
java
@RestController
@RequestMapping("/api/user")
public class UserController {
@GetMapping("/info")
public String getUserInfo(@RequestAttribute("username") String username) {
return "Current user: " + username;
}
}
4. 常见问题与解决方案
4.1 如何区分“未登录”和“令牌无效”?
拦截器可以返回不同的 HTTP 状态码:
-
401 Unauthorized:未提供令牌或令牌格式错误。
-
403 Forbidden:令牌有效但权限不足(需结合权限校验)。
java
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Missing token");
return false;
}
if (!jwtUtil.validateToken(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Invalid token");
return false;
}
4.2 如何支持 token 刷新?
-
方案一:在 token 过期前,客户端主动请求刷新接口,获取新 token。
-
方案二:在拦截器中检测到 token 即将过期时,通过响应头返回新 token(如
X-New-Token),客户端更新存储。
4.3 如何将用户信息传递给 Service 层?
推荐使用 ThreadLocal 或 RequestContextHolder 在任意层获取用户信息,避免在方法参数中层层传递。
java
public class UserContext {
private static final ThreadLocal<String> currentUser = new ThreadLocal<>();
public static void setCurrentUser(String username) {
currentUser.set(username);
}
public static String getCurrentUser() {
return currentUser.get();
}
public static void clear() {
currentUser.remove();
}
}
在拦截器中使用:
java
UserContext.setCurrentUser(username);
在拦截器的 afterCompletion 中清理:
java
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
UserContext.clear();
}
4.4 如何应对集群/分布式环境?
-
JWT 本身是无状态的,只要各节点使用相同的 secret 即可验证。
-
如需实现 token 黑名单(如注销),可使用 Redis 存储已注销的 token,拦截器校验时额外检查黑名单。
4.5 拦截器 vs Filter 如何选择?
-
Filter:基于 Servlet 规范,适用于所有请求(包括静态资源),更底层。
-
Interceptor:Spring 提供,可访问 Handler 信息,更便于与 Spring MVC 集成。
对于 JWT 校验,两者均可。但 Interceptor 可以更方便地获取 HandlerMethod 信息,便于实现细粒度的权限控制(如基于注解的权限校验)。
5. 进阶:结合注解实现细粒度权限控制
5.1 自定义注解
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireRole {
String[] value();
}
5.2 在拦截器中解析注解
在 preHandle 中获取 HandlerMethod,检查是否标注了 @RequireRole 注解,并根据用户角色决定是否放行。
java
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 只对 HandlerMethod 类型进行处理
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
RequireRole requireRole = handlerMethod.getMethodAnnotation(RequireRole.class);
if (requireRole == null) {
return true; // 没有权限注解,直接放行
}
// 从 token 中获取用户角色(假设 JWT 中包含 roles 字段)
String token = extractToken(request);
if (token == null || !jwtUtil.validateToken(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
List<String> roles = jwtUtil.getRolesFromToken(token);
boolean hasRole = Arrays.stream(requireRole.value()).anyMatch(roles::contains);
if (!hasRole) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return false;
}
return true;
}
5.3 在 Controller 中使用
java
@GetMapping("/admin")
@RequireRole("ADMIN")
public String adminOnly() {
return "Admin content";
}
6. 总结
-
JWT 提供了一种无状态的认证方式,适合分布式系统。
-
拦截器 是 Spring MVC 中实现请求拦截的利器,与 JWT 结合可以统一处理认证和授权。
-
关键点包括:token 的生成与验证、拦截器的路径配置、用户信息的传递以及异常处理。
-
进阶用法包括结合注解实现细粒度权限控制、支持 token 刷新、分布式环境下的黑名单机制等。
通过合理设计,拦截器与 JWT 的联合使用能够为应用提供简洁、高效、可扩展的认证授权解决方案。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)