Skip to content

JWT 生成与验证:jjwt 库使用

你有没有想过,为什么越来越多的系统选择 JWT 而不是传统的 Session?

Session 的问题在于:需要服务端存储,分布式环境下需要同步 session,扩展困难。

而 JWT 的核心优势就是无状态——服务端不需要存储任何东西,Token 本身包含了所有需要的信息。

今天,我们就来深入了解 JWT 的生成与验证。


JWT 是什么?

JWT 的全称

JSON Web Token——一种基于 JSON 的开放标准(RFC 7519),用于在各方之间安全地传输信息。

JWT 的结构

┌──────────────────────────────────────────────────────────────────────────┐
│                           JWT 结构                                       │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9                                 │
│  .                                                             │
│  eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5 │
│  MjJ9                                                             │
│  .                                                             │
│  SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c                        │
│                                                                          │
│  ──────────────────────────────────────────────────────────────────   │
│                                                                          │
│  Header.Payload.Signature                                              │
│                                                                          │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │ Header(头部)                                                    │  │
│  │ {                                                               │  │
│  │   "alg": "HS256",  ← 签名算法                                   │  │
│  │   "typ": "JWT"      ← Token 类型                                 │  │
│  │ }                                                               │  │
│  └──────────────────────────────────────────────────────────────────┘  │
│                                                                          │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │ Payload(载荷)                                                  │  │
│  │ {                                                               │  │
│  │   "sub": "1234567890",  ← Subject(用户标识)                   │  │
│  │   "name": "John Doe",     ← 自定义Claims                        │  │
│  │   "iat": 1516239022,      ← Issued At(签发时间)               │  │
│  │   "exp": 1516242622       ← Expiration(过期时间)              │  │
│  │ }                                                               │  │
│  └──────────────────────────────────────────────────────────────────┘  │
│                                                                          │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │ Signature(签名)                                                │  │
│  │ HMACSHA256(                                                    │  │
│  │   base64UrlEncode(header) + "." +                              │  │
│  │   base64UrlEncode(payload),                                    │  │
│  │   secret                                                        │  │
│  │ )                                                               │  │
│  └──────────────────────────────────────────────────────────────────┘  │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

JWT 的标准 Claims

Claim全称说明
issIssuer签发者
subSubject主题(用户标识)
audAudience受众
expExpiration Time过期时间
nbfNot Before生效时间
iatIssued At签发时间
jtiJWT ID唯一标识

自定义 Claims

除了标准 Claims,还可以添加自定义 Claims:

json
{
  "sub": "1234567890",
  "name": "John Doe",
  "role": "ADMIN",
  "userId": 1001,
  "department": "技术部"
}

jjwt 库使用

添加依赖

xml
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.12.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.12.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.12.5</version>
    <scope>runtime</scope>
</dependency>

生成 JWT

java
@Service
public class JwtService {
    
    // 签名密钥
    private static final String SECRET_KEY = "your-256-bit-secret-key-here-must-be-long-enough";
    
    /**
     * 生成 JWT Token
     */
    public String generateToken(String username, Long userId, List<String> roles) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + 86400000);  // 24小时
        
        return Jwts.builder()
            // 1. 设置 Header
            .header()
                .add("alg", "HS256")
                .add("typ", "JWT")
                .and()
            // 2. 设置 Payload(Claims)
            .subject(username)
            .claim("userId", userId)
            .claim("roles", roles)
            // 3. 设置标准 Claims
            .issuedAt(now)
            .expiration(expiryDate)
            // 4. 设置签名
            .signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8)))
            // 5. 生成
            .compact();
    }
}

解析 JWT

java
@Service
public class JwtService {
    
    /**
     * 解析 Token 获取 Claims
     */
    public Claims parseToken(String token) {
        return Jwts.parser()
            .verifyWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8)))
            .build()
            .parseSignedClaims(token)
            .getPayload();
    }
    
    /**
     * 获取用户名
     */
    public String getUsername(String token) {
        Claims claims = parseToken(token);
        return claims.getSubject();
    }
    
    /**
     * 获取用户 ID
     */
    public Long getUserId(String token) {
        Claims claims = parseToken(token);
        return claims.get("userId", Long.class);
    }
    
    /**
     * 获取角色
     */
    @SuppressWarnings("unchecked")
    public List<String> getRoles(String token) {
        Claims claims = parseToken(token);
        return claims.get("roles", List.class);
    }
    
    /**
     * 检查 Token 是否过期
     */
    public boolean isTokenExpired(String token) {
        try {
            Claims claims = parseToken(token);
            return claims.getExpiration().before(new Date());
        } catch (ExpiredJwtException e) {
            return true;
        }
    }
}

验证 JWT

java
@Service
public class JwtService {
    
    /**
     * 验证 Token
     */
    public boolean validateToken(String token) {
        try {
            Claims claims = parseToken(token);
            return !claims.getExpiration().before(new Date());
        } catch (JwtException e) {
            return false;
        }
    }
    
    /**
     * 验证 Token 并返回用户信息
     */
    public UserInfo validateAndGetUser(String token) {
        try {
            Claims claims = parseToken(token);
            
            // 检查是否过期
            if (claims.getExpiration().before(new Date())) {
                throw new TokenExpiredException("Token 已过期");
            }
            
            return UserInfo.builder()
                .username(claims.getSubject())
                .userId(claims.get("userId", Long.class))
                .roles(claims.get("roles", List.class))
                .build();
                
        } catch (ExpiredJwtException e) {
            throw new TokenExpiredException("Token 已过期");
        } catch (MalformedJwtException e) {
            throw new InvalidTokenException("Token 格式错误");
        } catch (JwtException e) {
            throw new InvalidTokenException("Token 无效");
        }
    }
}

使用 Builder 模式构建 Claims

定义用户信息类

java
@Data
@Builder
public class UserInfo {
    private Long userId;
    private String username;
    private String email;
    private List<String> roles;
    private Map<String, Object> additionalClaims;
}

生成 Token 的完整示例

java
@Service
public class JwtService {
    
    private static final long EXPIRATION_TIME = 86400000;  // 24小时
    private static final long REFRESH_EXPIRATION_TIME = 604800000;  // 7天
    
    private final Key key;
    
    public JwtService() {
        // 密钥至少需要 256 位
        this.key = Keys.hmacShaKeyFor(
            "your-256-bit-secret-key-here-must-be-long-enough-256-bits".getBytes()
        );
    }
    
    /**
     * 生成 Access Token(短期)
     */
    public String generateAccessToken(UserInfo user) {
        Date now = new Date();
        Date expiry = new Date(now.getTime() + EXPIRATION_TIME);
        
        return Jwts.builder()
            .subject(user.getUsername())
            .claim("userId", user.getUserId())
            .claim("email", user.getEmail())
            .claim("roles", user.getRoles())
            // 添加自定义 Claims
            .claim("type", "access")
            .issuedAt(now)
            .expiration(expiry)
            // 添加 ID(用于 Token 注销)
            .id(UUID.randomUUID().toString())
            .signWith(key)
            .compact();
    }
    
    /**
     * 生成 Refresh Token(长期)
     */
    public String generateRefreshToken(UserInfo user) {
        Date now = new Date();
        Date expiry = new Date(now.getTime() + REFRESH_EXPIRATION_TIME);
        
        return Jwts.builder()
            .subject(user.getUsername())
            .claim("userId", user.getUserId())
            .claim("type", "refresh")
            .issuedAt(now)
            .expiration(expiry)
            .id(UUID.randomUUID().toString())
            .signWith(key)
            .compact();
    }
}

异常处理

JWT 异常类型

java
public class JwtExceptionHandler {
    
    @ExceptionHandler(ExpiredJwtException.class)
    public Result<Void> handleExpired(ExpiredJwtException e) {
        return Result.fail(401, "Token 已过期,请重新登录");
    }
    
    @ExceptionHandler(MalformedJwtException.class)
    public Result<Void> handleMalformed(MalformedJwtException e) {
        return Result.fail(401, "Token 格式错误");
    }
    
    @ExceptionHandler(SignatureException.class)
    public Result<Void> handleSignature(SignatureException e) {
        return Result.fail(401, "Token 签名验证失败");
    }
    
    @ExceptionHandler(UnsupportedJwtException.class)
    public Result<Void> handleUnsupported(UnsupportedJwtException e) {
        return Result.fail(401, "不支持的 Token 格式");
    }
    
    @ExceptionHandler(IllegalArgumentException.class)
    public Result<Void> handleIllegal(IllegalArgumentException e) {
        return Result.fail(401, "Token 不能为空");
    }
}

JWT 的安全问题

1. 密钥安全

java
// ❌ 硬编码密钥(危险)
private static final String SECRET = "hardcoded-secret-key";

// ✅ 从配置文件读取
@Value("${jwt.secret}")
private String secret;

// ✅ 使用环境变量
private String secret = System.getenv("JWT_SECRET");

// ✅ 使用配置中心(如 Apollo、Nacos)
@Value("${jwt.secret}")
private String secret;

2. 使用 HTTPS

JWT 通过 HTTP 传输时,必须使用 HTTPS 防止中间人攻击。

yaml
# application.yml
server:
  ssl:
    enabled: true

3. 敏感信息

java
// ❌ 在 Payload 中存储敏感信息
.claim("password", user.getPassword())
.claim("creditCard", "1234-5678-9012-3456")

// ✅ 只存储必要信息
.claim("userId", user.getId())
.claim("email", user.getEmail())  // 公开的信息可以

完整示例

JWT 工具类

java
@Service
public class JwtService {
    
    private static final String SECRET = System.getenv("JWT_SECRET");
    private static final long ACCESS_EXPIRATION = 3600000;      // 1小时
    private static final long REFRESH_EXPIRATION = 604800000;   // 7天
    
    private final Key key;
    
    public JwtService() {
        this.key = Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8));
    }
    
    /**
     * 生成 Access Token
     */
    public String generateAccessToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("roles", userDetails.getAuthorities()
            .stream()
            .map(GrantedAuthority::getAuthority)
            .collect(Collectors.toList()));
        claims.put("type", "access");
        
        return createToken(claims, userDetails.getUsername(), ACCESS_EXPIRATION);
    }
    
    /**
     * 生成 Refresh Token
     */
    public String generateRefreshToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("type", "refresh");
        
        return createToken(claims, userDetails.getUsername(), REFRESH_EXPIRATION);
    }
    
    private String createToken(Map<String, Object> claims, String subject, long expiration) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + expiration);
        
        return Jwts.builder()
            .claims(claims)
            .subject(subject)
            .issuedAt(now)
            .expiration(expiryDate)
            .id(UUID.randomUUID().toString())
            .signWith(key)
            .compact();
    }
    
    /**
     * 验证并解析 Token
     */
    public Claims validateToken(String token) {
        return Jwts.parser()
            .verifyWith(key)
            .build()
            .parseSignedClaims(token)
            .getPayload();
    }
    
    /**
     * 获取用户名
     */
    public String getUsernameFromToken(String token) {
        return validateToken(token).getSubject();
    }
    
    /**
     * 获取 Token ID(用于黑名单)
     */
    public String getTokenId(String token) {
        return validateToken(token).getId();
    }
}

面试追问方向

问题考察点延伸阅读
JWT 的结构是什么?基础概念本篇
JWT 和 Session 的区别?对比理解JWT vs Session
JWT 为什么不需要在服务端存储?原理理解本篇
JWT 的安全问题有哪些?安全意识本篇
Access Token 和 Refresh Token 的区别?设计理解本篇

总结

JWT 生成与验证的核心要点:

  1. JWT 结构:Header.Payload.Signature
  2. jjwt 库:提供简洁的 API 生成和解析 JWT
  3. 标准 Claims:iss、sub、aud、exp、nbf、iat、jti
  4. 签名算法:HS256 需要至少 256 位密钥
  5. 安全要点:密钥安全、HTTPS、不存敏感信息

JWT 是现代无状态认证的核心技术,理解其原理和使用方式至关重要。


下一步

基于 VitePress 构建