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. 联合使用的工作流程

  1. 用户登录:客户端发送用户名/密码,服务器验证通过后生成 JWT 并返回。

  2. 客户端存储 JWT:通常存储在 localStorage 或 HttpOnly Cookie 中。

  3. 携带 JWT 请求:客户端在后续请求的 Authorization 头中添加 Bearer <JWT>

  4. 拦截器拦截请求:对于需要认证的接口,拦截器从请求头中提取 JWT,验证其有效性。

  5. 验证通过:将用户信息(如用户 ID)存入 ThreadLocal 或请求属性,供后续业务逻辑使用。

  6. 验证失败:返回 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 的联合使用能够为应用提供简洁、高效、可扩展的认证授权解决方案。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐