JWT 是 Java Web 项目最常用的身份认证方案。但市面上大多数 JWT 库要么依赖太重,要么安全默认值不够严格。
引入依赖,直接开始用
Spring Boot 2.x 项目:
<dependency>
<groupId>com.gitee.apanlh</groupId>
<artifactId>apanlh-common</artifactId>
<version>2.0.6</version>
</dependency>
Spring Boot 3.x 项目:
<dependency>
<groupId>com.gitee.apanlh</groupId>
<artifactId>apanlh-common</artifactId>
<version>3.0.6</version>
</dependency>
JDK 8+ 可用,零强制第三方依赖。引了就能用。
最简单的用法:3 分钟上手
签发 Token
import com.gitee.apanlh.util.algorithm.jwt.JwtBuilder;
import com.gitee.apanlh.util.algorithm.jwt.JwtAlgorithm;
import java.util.concurrent.TimeUnit;
// 选一个算法,配置密钥
JwtAlgorithm alg = JwtAlgorithm.HS256("your-secret-key-at-least-32-bytes!!");
// 构建 Token
String token = JwtBuilder.builder(alg)
.subject("user-123")
.issuer("my-app")
.claim("role", "admin")
.expiresAt(1, TimeUnit.HOURS) // 1 小时过期
.build();
System.out.println(token);
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTEyMyIsImlzcyI6Im15LWFwcCIsImV4cCI6MT…
验证 Token
import com.gitee.apanlh.util.algorithm.jwt.JwtVerifier;
import com.gitee.apanlh.util.algorithm.jwt.JwtVerifyResult;
JwtVerifyResult result = JwtVerifier.builder(alg)
.expectedIssuer("my-app")
.build()
.verify(token);
result.getSubject(); // "user-123"
result.getIssuer(); // "my-app"
result.getClaim("role"); // "admin"
签发和验证用的是同一个 alg 实例,算法和密钥绑在一起,不会出现配置不一致的问题。
一行快捷验证
// 一行搞定,等价于 builder(alg).build().verify(token)
JwtVerifyResult result = JwtVerifier.verify(token, alg);
三大算法族
HMAC 对称加密
最常用,签发和验证用同一把密钥:
// HS256(密钥至少 32 字节)
JwtAlgorithm alg = JwtAlgorithm.HS256("your-secret-key-at-least-32-bytes!!");
// HS384(密钥至少 48 字节)
JwtAlgorithm alg = JwtAlgorithm.HS384("your-secret-key-at-least-48-bytes-long-for-hs384!!");
// HS512(密钥至少 64 字节)
JwtAlgorithm alg = JwtAlgorithm.HS512("your-secret-key-at-least-64-bytes-long-for-hs512-algorithm!!");
密钥太短会自动拒绝:
JwtAlgorithm.HS256("short"); // 报错:HS256 requires at least 32 bytes
RSA 非对称加密
适合签发服务和验证网关分离的场景:
// 签发端:持有私钥,可签发可验证
JwtAlgorithm signer = JwtAlgorithm.RSA256(privateKey, publicKey);
JwtAlgorithm signerOnly = JwtAlgorithm.RSA256Signer(privateKey);
// 验证端:只持有公钥,只能验证
JwtAlgorithm verifier = JwtAlgorithm.RSA256Verifier(publicKey);
同样支持 RS384 和 RS512,RSA 密钥至少 2048 位,不足的会自动拒绝。
ECDSA 非对称加密
适合对 Token 体积有要求的场景(签名更短):
// ES256(P-256 曲线)
JwtAlgorithm alg = JwtAlgorithm.ECDSA256(privateKey, publicKey);
// ES384(P-384 曲线)
JwtAlgorithm alg = JwtAlgorithm.ECDSA384(privateKey, publicKey);
// ES512(P-521 曲线)
JwtAlgorithm alg = JwtAlgorithm.ECDSA512(privateKey, publicKey);
内置 DER 转 JWS 签名格式转换,不需要 BouncyCastle,曲线不匹配会自动拒绝。
完整签发示例
JwtAlgorithm alg = JwtAlgorithm.HS256("your-secret-key-at-least-32-bytes!!");
String token = JwtBuilder.builder(alg)
// 标准 claims
.subject("user-123") // 主题
.issuer("my-app") // 签发者
.audience("web-client") // 受众
.expiresAt(1, TimeUnit.HOURS) // 签发后 1 小时过期
.notBefore(10, TimeUnit.MINUTES) // 签发后 10 分钟才生效
.generateJti() // 自动生成 UUID 作为 JWT ID
// 自定义 claims
.claim("role", "admin") // 字符串
.claim("level", 10) // 数值
.claim("vip", true) // 布尔值
// 额外 Header
.headerParam("kid", "key-v1")
.build();
完整验证示例
JwtAlgorithm alg = JwtAlgorithm.HS256("your-secret-key-at-least-32-bytes!!");
JwtVerifyResult result = JwtVerifier.builder(alg)
// 验证签发者、受众、主题
.expectedIssuer("my-app")
.expectedAudience("web-client")
.expectedSubject("user-123")
// 验证自定义 claims
.expectedClaims(Map.of("role", "admin"))
// 时钟偏差容忍(服务端时钟可能略有偏差)
.allowedClockSkew(60, TimeUnit.SECONDS)
// 限制最大 Token 有效期(防止签发超长有效期的 Token)
.maxTokenLifetime(24, TimeUnit.HOURS)
// 自定义拦截器:Token 黑名单 / JTI 防重放
.interceptor((header, payload) -> {
String jti = payload.getJwtId();
if (blacklist.contains(jti)) {
throw new JwtException("token has been revoked");
}
})
.build()
.verify(token);
// 获取验证结果
result.getSubject(); // "user-123"
result.getIssuer(); // "my-app"
result.getClaim("role"); // "admin"
result.getPayload(); // 完整 Payload 对象
安全方面
none 算法攻击默认防御
构建验证器时,算法白名单默认只允许构建时指定的算法。你用什么算法构建验证器,Token 就必须用什么算法签名,其他算法一律拒绝。
算法混淆攻击默认防御
RSA 公钥是公开的,攻击者可以用它当 HMAC 密钥签一个 HS256 的 Token。但由于白名单锁死了算法,HS256 的 Token 在第一关就会被拒绝。
时序攻击防御
签名比较使用 MessageDigest.isEqual() 逐字节恒定时间比较,不因字节差异提前返回,防止通过响应时间差异猜测签名。
弱密钥自动拒绝
- HMAC 密钥:HS256 至少 32 字节,HS384 至少 48 字节,HS512 至少 64 字节
- RSA 密钥:至少 2048 位
- ECDSA:必须匹配对应曲线(ES256 用 P-256,ES384 用 P-384,ES512 用 P-521)
密钥太短或曲线不对会直接拒绝,不会签发成功。
Token 格式安全
- 不用正则 split 分割 Token(用 indexOf,无 ReDoS 风险)
- 每段限制 8KB 以内,防止超长 Payload 消耗内存
- alg 和 typ 是保留字段,不能通过 headerParam() 注入
Claim 安全
- 自定义 claim 只接受 String、Number、Boolean,拒绝复杂对象
- 解析 Header 时会丢弃所有非标准参数(kid、jku、x5u 等),防止注入
兼容遗留系统:Lenient 宽松模式
如果项目还在用短密钥或弱 RSA 密钥,提供了 Lenient 后缀的工厂方法:
JwtAlgorithm.HS256Lenient("short"); // 跳过长度校验
JwtAlgorithm.RSA256Lenient(privateKey, publicKey); // 跳过 2048 位校验
JwtAlgorithm.ECDSA256Lenient(privateKey, publicKey); // 跳过曲线校验
新系统必须用标准模式。
实用功能
自动生成 JTI
// UUID
JwtBuilder.builder(alg).generateJti().build();
// 雪花 ID
JwtBuilder.builder(alg).generateSnowFlakeJti().build();
// 指定机器节点的雪花 ID
JwtBuilder.builder(alg).generateSnowFlakeJti(5, 31).build();
不适合的场景
- 不支持 JWE(加密 JWT)
- 不支持 jku、x5u 等 Header 参数(解析时会被丢弃)
- 不替代 Spring Security OAuth2 Resource Server
项目地址: https://gitee.com/apanlh/pan-common
项目文档: https://gitee.com/apanlh/pan-common/wikis/首页





