JWT Builder 代码完全解析 - 从入门到精通 JWT 令牌构建 🔐
📋 摘要
深入解析 JWT Builder 代码的每个环节,从 Jwts.builder() 到 .compact() 的完整流程,让你彻底掌握 JWT 令牌构建原理!
🚀 JWT Builder 代码详解
核心代码分析
Jwts.builder()
.claims(claims)
.subject(subject)
.issuedAt(now)
.expiration(expirationDate)
.signWith(getSigningKey())
.compact();
这段代码是使用 JJWT 库构建 JWT 令牌的标准模式。让我们逐步深入分析每个方法的作用和原理。
🔍 方法详解
1. Jwts.builder() - 构建器初始化
Jwts.builder()
作用:创建一个 JwtBuilder 实例,这是构建 JWT 的起点。
特点:
- 🎯 采用建造者模式(Builder Pattern)
- 🔧 提供链式调用接口
- 📦 内部维护 JWT 的各个组成部分
示例:
JwtBuilder builder = Jwts.builder();
2. .claims(claims) - 自定义声明设置
.claims(claims)
作用:设置 JWT 的自定义声明(Claims),这些是 JWT 的核心数据部分。
参数说明:
claims:Map<String, Object>类型,包含键值对数据
常用声明类型:
Map<String, Object> claims = new HashMap<>();
claims.put("userId", 12345);
claims.put("role", "admin");
claims.put("permissions", Arrays.asList("read", "write", "delete"));
claims.put("department", "IT");
实际应用场景:
- 👤 用户身份信息
- 🔑 权限和角色
- 📊 业务相关数据
- 🏢 组织架构信息
3. .subject(subject) - 主题设置
.subject(subject)
作用:设置 JWT 的主题(Subject),通常用于标识令牌的拥有者。
参数说明:
subject:String类型,通常是用户的唯一标识符
常见用法:
.subject("user123") // 用户 ID
.subject("admin@company.com") // 邮箱地址
.subject("employee_001") // 员工编号
标准声明:
- 📝 这是 JWT 标准中的
sub字段 - 🎯 用于标识 JWT 面向的主体
- 🔍 在验证时用于快速识别用户
4. .issuedAt(now) - 签发时间设置
.issuedAt(now)
作用:设置 JWT 的签发时间(Issued At),记录令牌的创建时间。
参数说明:
now:Date类型,表示当前时间
代码示例:
Date now = new Date();
// 或者
Date now = Date.from(Instant.now());
重要性:
- ⏰ 用于计算令牌年龄
- 🔒 配合过期时间进行安全控制
- 📈 便于审计和日志记录
- 🛡️ 防止重放攻击
5. .expiration(expirationDate) - 过期时间设置
.expiration(expirationDate)
作用:设置 JWT 的过期时间,确保令牌不会永久有效。
参数说明:
expirationDate:Date类型,表示过期时间
时间计算示例:
// 24 小时后过期
Date expirationDate = new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000);
// 7 天后过期
Date expirationDate = new Date(System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000);
// 使用 Java 8+ 时间 API
Date expirationDate = Date.from(Instant.now().plus(1, ChronoUnit.DAYS));
安全建议:
- ⏱️ 访问令牌:15 分钟 - 2 小时
- 🔄 刷新令牌:7 天 - 30 天
- 🎫 临时令牌:5 分钟 - 1 小时
6. .signWith(getSigningKey()) - 签名设置
.signWith(getSigningKey())
作用:使用指定密钥对 JWT 进行签名,确保令牌的完整性和真实性。
密钥获取方法:
private Key getSigningKey() {
// HMAC SHA-256 密钥
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
return Keys.hmacShaKeyFor(keyBytes);
// RSA 私钥
return Keys.privateKeyFromPem(privateKeyPem);
// 简单字符串密钥(不推荐生产环境)
return Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8));
}
签名算法选择:
- 🔐 HS256:HMAC SHA-256(对称加密)
- 🔐 RS256:RSA SHA-256(非对称加密)
- 🔐 ES256:ECDSA SHA-256(椭圆曲线)
7. .compact() - 生成最终令牌
.compact()
作用:将构建的 JWT 压缩为紧凑的字符串格式,便于传输和存储。
返回结果:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTY4NDM4MjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
格式说明:
- 📦 Header(头部)
- 📦 Payload(载荷)
- 📦 Signature(签名)
- 🔗 三部分用
.分隔
🎨 JWT 构建流程图
graph TD
A[Jwts.builder] --> B[设置 Claims]
B --> C[设置 Subject]
C --> D[设置签发时间]
D --> E[设置过期时间]
E --> F[设置签名密钥]
F --> G[compact 生成令牌]
B --> B1[用户信息]
B --> B2[权限数据]
B --> B3[业务数据]
C --> C1[用户 ID]
C --> C2[邮箱地址]
D --> D1[当前时间]
E --> E1[过期时间计算]
F --> F1[HMAC 密钥]
F --> F2[RSA 密钥]
G --> H[Base64 编码字符串]
style A fill:#e1f5fe
style G fill:#c8e6c9
style H fill:#fff3e0
💡 完整代码示例
基础示例
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JWTBuilderExample {
private static final String SECRET_KEY = "mySecretKey123456789012345678901234567890";
public String createJWT(String userId, String role) {
// 1. 准备声明数据
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
claims.put("role", role);
claims.put("permissions", Arrays.asList("read", "write"));
// 2. 计算时间
Date now = new Date();
Date expirationDate = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 24 小时
// 3. 构建 JWT
return Jwts.builder()
.claims(claims)
.subject(userId)
.issuedAt(now)
.expiration(expirationDate)
.signWith(getSigningKey())
.compact();
}
private Key getSigningKey() {
byte[] keyBytes = SECRET_KEY.getBytes(StandardCharsets.UTF_8);
return Keys.hmacShaKeyFor(keyBytes);
}
}
高级示例 - 带刷新令牌
public class AdvancedJWTBuilder {
public TokenPair createTokenPair(String userId, String role) {
Date now = new Date();
// 访问令牌(1 小时)
String accessToken = Jwts.builder()
.subject(userId)
.claim("type", "access")
.claim("role", role)
.issuedAt(now)
.expiration(new Date(now.getTime() + 60 * 60 * 1000))
.signWith(getSigningKey())
.compact();
// 刷新令牌(7 天)
String refreshToken = Jwts.builder()
.subject(userId)
.claim("type", "refresh")
.issuedAt(now)
.expiration(new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000))
.signWith(getSigningKey())
.compact();
return new TokenPair(accessToken, refreshToken);
}
}
🔧 最佳实践
1. 密钥管理
// ✅ 推荐:使用环境变量
private static final String SECRET_KEY = System.getenv("JWT_SECRET_KEY");
// ✅ 推荐:使用配置文件
@Value("${jwt.secret}")
private String secretKey;
// ❌ 不推荐:硬编码密钥
private static final String SECRET_KEY = "hardcoded_secret";
2. 时间处理
// ✅ 推荐:使用 Java 8+ 时间 API
Date now = Date.from(Instant.now());
Date expiration = Date.from(Instant.now().plus(1, ChronoUnit.HOURS));
// ✅ 推荐:使用配置化的过期时间
@Value("${jwt.expiration}")
private long expirationTime;
3. 异常处理
public String createJWT(String userId) {
try {
return Jwts.builder()
.subject(userId)
.issuedAt(new Date())
.expiration(calculateExpiration())
.signWith(getSigningKey())
.compact();
} catch (Exception e) {
log.error("JWT 创建失败: {}", e.getMessage());
throw new JWTException("令牌生成失败");
}
}
🛡️ 安全注意事项
1. 密钥安全
- 🔐 使用足够长度的密钥(至少 256 位)
- 🔄 定期轮换密钥
- 🏢 使用密钥管理服务(如 AWS KMS)
2. 过期时间设置
- ⏰ 访问令牌:短时间(15 分钟 - 2 小时)
- 🔄 刷新令牌:较长时间(7 天 - 30 天)
- 🎫 临时令牌:极短时间(5 分钟)
3. 敏感信息处理
- ❌ 不要在 JWT 中存储敏感信息
- ✅ 只存储必要的用户标识信息
- 🔒 使用 HTTPS 传输 JWT
🎯 常见问题解答
Q1: 为什么需要设置 issuedAt 和 expiration?
A: 这两个时间字段提供了重要的安全控制:
issuedAt用于计算令牌年龄,防止重放攻击expiration确保令牌不会永久有效,提高安全性
Q2: claims 中可以存储哪些类型的数据?
A: claims 支持多种数据类型:
- 基本类型:
String、Number、Boolean - 集合类型:
List、Map - 自定义对象(会被序列化为 JSON)
Q3: 如何选择合适的签名算法?
A: 根据使用场景选择:
- HS256:单服务应用,密钥共享
- RS256:微服务架构,公钥分发
- ES256:移动应用,性能要求高
📊 性能优化建议
1. 缓存签名密钥
private static final Key SIGNING_KEY = Keys.hmacShaKeyFor(
System.getenv("JWT_SECRET").getBytes(StandardCharsets.UTF_8)
);
2. 减少 Claims 大小
// ✅ 推荐:只存储必要信息
claims.put("uid", userId); // 而不是完整的用户对象
// ❌ 不推荐:存储大量数据
claims.put("userProfile", largeUserObject);
3. 批量处理
public List<String> createMultipleJWTs(List<String> userIds) {
return userIds.stream()
.map(this::createJWT)
.collect(Collectors.toList());
}
🎉 总结
通过深入学习 JWT Builder 代码的每个环节,你已经掌握了:
- 🎯 核心方法:从
builder()到compact()的完整流程 - 🔧 实用技巧:时间计算、密钥管理、异常处理
- 🛡️ 安全实践:密钥安全、过期时间、敏感信息处理
- ⚡ 性能优化:缓存、批量处理、大小控制
JWT 是现代 Web 应用不可或缺的身份认证技术,掌握其构建原理将大大提升你的开发能力。继续深入学习 JWT 的解析和验证,你将成为身份认证领域的专家!🚀
记住:安全第一,性能第二。在构建 JWT 时,始终将安全性放在首位,合理设置过期时间和密钥管理策略。加油,未来的架构师!💪
厦门工学院人工智能创作坊 -- 郑恩赐
2025 年 10 月 7 日