JWT Token 生成/解析/校验工具类
·
JWT Token 生成/解析/校验工具类
概述
在 Spring Boot 项目中实现基于 JWT 的认证机制时,核心工作集中在 Token 的生成、解析和校验三个环节。本文介绍一个基于 JJWT 0.12.x 的工具类实现,以及对应的 YAML 配置方式。
依赖引入
//jwt(jjwt 0.12.x)
implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5'
JJWT 从 0.12.x 开始将 API 与实现分离,jjwt-jackson 用于 JSON 序列化/反序列化的支持,runtimeOnly 作用域即可。
工具类实现
完整代码
package com.example.project.util;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
/**
* JWT工具类(使用jjwt 0.12.x API)
*/
@Component
public class JwtUtil {
private final SecretKey secretKey;
private final long expirationMs;
public JwtUtil(@Value("${jwt.secret}") String secret,
@Value("${jwt.expiration-ms}") long expirationMs) {
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
this.expirationMs = expirationMs;
}
/**
* 生成Token
* @param userId 用户ID,作为subject存储
* @param username 用户名,作为自定义claim存储
* @return 签发后的JWT字符串
*/
public String generateToken(Long userId, String username) {
Date now = new Date();
return Jwts.builder()
.subject(String.valueOf(userId))
.claim("username", username)
.issuedAt(now)
.expiration(new Date(now.getTime() + expirationMs))
.signWith(secretKey)
.compact();
}
/**
* 从Token中解析用户ID
* @param token JWT字符串
* @return 用户ID
*/
public Long getUserId(String token) {
return Long.parseLong(
Jwts.parser().verifyWith(secretKey).build()
.parseSignedClaims(token).getPayload().getSubject()
);
}
/**
* 从Token中解析用户名
* @param token JWT字符串
* @return 用户名
*/
public String getUsername(String token) {
return Jwts.parser().verifyWith(secretKey).build()
.parseSignedClaims(token).getPayload().get("username", String.class);
}
/**
* 校验Token是否合法
* @param token JWT字符串
* @return true-合法 / false-非法或已过期
*/
public boolean validateToken(String token) {
try {
Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
public long getExpirationMs() {
return expirationMs;
}
}
关键设计点
1. 密钥处理
HMAC-SHA 算法要求密钥长度至少为 256 位(32 字节)。代码中通过 Keys.hmacShaKeyFor() 将配置文件中的字符串转换为 SecretKey 对象:
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
这里直接指定 StandardCharsets.UTF_8,避免依赖系统默认编码,确保跨环境一致性。
2. Token 结构
生成的 Token 包含以下字段:
| 字段 | 存储位置 | 说明 |
|---|---|---|
| userId | subject (sub) | JWT 标准声明,存放用户唯一标识 |
| username | 自定义 claim | 额外携带的用户信息 |
| issuedAt | 标准声明 (iat) | Token 签发时间 |
| expiration | 标准声明 (exp) | Token 过期时间 |
将 userId 放入 subject 而非自定义 claim,是遵循 JWT 规范的常见做法——subject 字段的语义就是标识 Token 的主体身份。
3. 解析与校验的分离
代码中 validateToken 和 getUserId/getUsername 在功能上有重叠:解析方法本身就会执行签名验证和过期检查。这种设计的考虑在于:
- 校验接口用于过滤器链中的快速判断,只需知道合法与否
- 解析接口用于业务层提取身份信息,同时完成合法性验证
实际调用时无需先校验再解析,直接调用解析方法即可获得相同的安全保障。
4. 异常处理
validateToken 方法捕获两类异常:
- JwtException:签名无效、格式错误、Token 过期等 JWT 相关异常
- IllegalArgumentException:传入 null 或空字符串时抛出
返回布尔值而非抛异常,是为了让上层调用方(如 Filter)能根据返回值灵活决定响应策略。
配置文件
在 application.yml 中添加以下配置:
spring:
application:
name: reference-self
profiles:
active: dev
# ---- JWT 配置 ----
jwt:
secret: "SpringBootTutorialSecretKey_MinLength_32chars_OK"
expiration-ms: 86400000 # 24小时,单位:毫秒
几点说明:
- secret:HMAC 密钥,生产环境应从环境变量或密钥管理服务读取,不应明文写在配置文件中。最小长度 32 字符(256 位)
- expiration-ms:Token 有效期,示例值为 24 小时(86400000 毫秒)。根据业务场景可调整为更短的时间以增强安全性
使用示例
登录签发 Token
@Service
@RequiredArgsConstructor
public class AuthService {
private final JwtUtil jwtUtil;
public String login(LoginRequest request) {
// 1. 校验用户名密码(省略)
// 2. 生成Token并返回
return jwtUtil.generateToken(user.getId(), user.getUsername());
}
}
请求拦截校验
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) {
String token = extractToken(request); // 从Header中提取
if (token != null && jwtUtil.validateToken(token)) {
Long userId = jwtUtil.getUserId(token);
// 设置安全上下文(省略)
}
chain.doFilter(request, response);
}
}
业务层获取用户信息
@GetMapping("/profile")
public ResponseEntity<UserProfile> getProfile(HttpServletRequest request) {
String token = extractToken(request);
Long userId = jwtUtil.getUserId(token);
String username = jwtUtil.getUsername(token);
// 查询并返回用户信息
}
常见问题
Token 过期后如何处理?
当前实现为无状态 Token,过期后只能重新登录签发。如果需要续期能力,通常有两种方案:
- 缩短有效期 + Refresh Token:Access Token 有效期设为 15-30 分钟,配合较长期的 Refresh Token 实现无感刷新
- 滑动窗口:每次请求成功后重新签发新 Token,前端替换本地存储的旧 Token
生产环境的密钥管理?
开发阶段可以硬编码在 yml 中,但生产环境建议通过以下任一方式注入:
# 方式一:环境变量
export JWT_SECRET="your-production-secret-key-at-least-32-chars"
# 方式二:启动参数
java -jar app.jar --jwt.secret="${JWT_SECRET}"
yml 中引用:
jwt:
secret: ${JWT_SECRET:default-fallback-key}
更多推荐




所有评论(0)