JJWT 0.13.0:显式固定签名算法,按 key 类型验签
工程笔记
发布 / DavidHLPL
结论优先:JJWT 0.13.0 的 signWith(Key) 会根据 key 的算法名和强度选择推荐算法;如果协议要求固定 HS256、RS256 或 PS*,应使用 signWith(key, Jwts.SIG.*)。验签不要用私钥或已弃用的通用 setSigningKey(Key):使用 verifyWith(SecretKey) 或 verifyWith(PublicKey),并把 token header 的 alg 与服务端允许的算法策略分开审计。
适用版本与场景
本页只对 JJWT 0.13.0 固定 tag 负责,适用于:
- 从旧版 JJWT 迁移到 0.12/0.13 API;
- 需要核对签发端到底使用了哪一个 HMAC/RSA 算法;
- 需要区分签名 key、验签 key 和
audclaim builder; - 排查“同一套代码换了 key 长度后 token 的
alg发生变化”。
JJWT 1.0 可能移除本页提到的弃用 API;升级后要重新核对源码。
1. 不要把 signWith(Key) 当作协议配置
JJWT 0.13.0 的单参数入口是:
JwtBuilder signWith(Key key) throws InvalidKeyException;
它使用 key 的推荐签名算法:
SecretKey根据 JCA 算法名HmacSHA256/HmacSHA384/HmacSHA512和位长区间选择HS256(256–383 位)、HS384(384–511 位)、HS512(≥512 位);- RSA
PrivateKey按位长区间推荐:2048–3071 →RS256、3072–4095 →RS384、≥4096 →RS512; PS256、PS384、PS512不由这套启发式自动选择;- key 算法名不匹配(HMAC key 的 JCA 名非
HmacSHA*)或强度不足(HMAC <256 位、RSA <2048 位)时会失败,而不是安全地“随便换一个算法”;以上位长区间是signWith(Key)Javadoc 推荐表,impl 的 HMAC 选择在 JCA 名匹配后只做最低位长检查,不校验区间上限,因此区间上限是文档建议而非强制边界。
因此,单参数写法适合接受 JJWT 的推荐规则,不适合作为跨服务协议的唯一声明。密钥长度、JCA 算法名或生成方式变化,都可能改变推荐结果。
2. 协议需要固定算法时用双参数 API
Jwts.builder()
.subject("subject")
.signWith(rsaPrivateKey, Jwts.SIG.RS256)
.compact();
0.12.0 起推荐的签名入口是泛型 signWith(K, SecureDigestAlgorithm<? super K, ?>)。Jwts.SIG.RS256 是类型安全的 SignatureAlgorithm 实例;JJWT 仍会检查 key 是否允许用于该算法,所以显式指定不是绕过 key 类型和强度限制。
HMAC key 应使用满足算法要求的二进制密钥,例如:
SecretKey key = Keys.hmacShaKeyFor(randomBytes);
String token = Jwts.builder()
.subject("subject")
.signWith(key, Jwts.SIG.HS256)
.compact();
不要把普通密码字符串直接作为 HMAC key。Keys.hmacShaKeyFor(byte[]) 会在长度不足时抛 WeakKeyException。
旧 API 迁移
| 旧写法 | 推荐写法 |
|---|---|
signWith(SignatureAlgorithm.HS256, ...) |
signWith(key, Jwts.SIG.HS256) |
signWith(key, oldSignatureAlgorithm) |
signWith(key, Jwts.SIG.RS256) 等 |
旧的 signWith(Key, SignatureAlgorithm) 在 0.13.0 仍存在,但已经弃用,目标是 1.0 前移除。
3. verifyWith 以 key 类型划分职责
JwtParser parser = Jwts.parser()
.verifyWith(publicKey)
.build();
非弃用 API 只有两类:
verifyWith(SecretKey):用于 MAC 类算法,如HS256、HS384、HS512;verifyWith(PublicKey):用于 RSA、PSS、EC、EdDSA 等非对称签名;PrivateKey不能用于验签:非弃用verifyWith无PrivateKey重载,编译期即失败;旧setSigningKey(Key)对PrivateKey抛InvalidKeyException。
验签算法不是由 key 自动推断的。解析器按 JWS header 的 alg 查找算法,再用配置的 key 验证;如果 header、key 类型或实际算法不匹配,验签失败。也就是说:
- 签发端用双参数 API 固定协议算法;
- 验签端按服务类型配置 SecretKey 或 PublicKey;
- 另行审计允许的
alg集合,不要把“能验签”误当作“符合协议”。
旧验签 API 迁移
// 旧 API:已弃用
Jwts.parser().setSigningKey(key);
// HMAC
Jwts.parser().verifyWith(secretKey).build();
// RSA/EC/EdDSA
Jwts.parser().verifyWith(publicKey).build();
通用 setSigningKey(Key) 的问题不是“不能工作”,而是把本应显式区分的 key 类型隐藏在运行时转发中。
4. aud 使用集合式 builder
String token = Jwts.builder()
.subject("subject")
.audience()
.add("service-a")
.add("service-b")
.and()
.signWith(key, Jwts.SIG.HS256)
.compact();
audience() 返回 AudienceCollection<JwtBuilder>;add 会忽略 null、空白和已存在值,and() 回到父 builder。setAudience(String) 已弃用,single(String) 只为 RFC 允许的单字符串接收方保留。
注意:写入 aud claim 不等于完成业务侧 audience 校验。解析后仍需按应用的 issuer、audience、有效期和密钥轮换策略验证 claims;这些策略不由本页的 builder API 自动决定。
最小验证清单
在隔离测试中保留以下四个断言:
- 32 字节 HMAC key 经
signWith(key)签发后,headeralg为HS256; signWith(privateKey, Jwts.SIG.RS256)与verifyWith(publicKey)可以完成 round-trip;setSigningKey(rsaPrivateKey)(旧 API)必须失败(InvalidKeyException);非弃用verifyWith(rsaPrivateKey)无法编译。audience().add("service-a").and()不会重复加入空白或重复 audience。
本批只固定了 API、Javadoc、实现源码和 README 证据,未执行上述编译/运行测试;测试结果不能从静态源码推断。
常见误区与边界
- 误区: key 足够长就一定自动得到想要的算法。事实: 单参数入口还依赖 key 的 JCA 算法名和 JJWT 的推荐规则;协议算法应显式传入。
- 误区:
verifyWith会根据公钥决定 token 的算法。事实: parser 先读取 JWS header 的alg,再执行匹配校验。 - 误区:
PrivateKey也可以验签,因为它包含公钥信息。事实: 非弃用verifyWith没有PrivateKey重载,编译期即拒绝;旧setSigningKey(Key)对PrivateKey在运行时抛InvalidKeyException。 - 误区:
audience().add就等于完成 audience 授权。事实: builder 只写 claim,业务授权仍需独立校验。 - 本页不覆盖 JWE、JWKS 轮换、密钥定位器和具体 issuer/audience 校验实现。
证据与相关页面
- 来源事实: 初始 raw 与
jjwt-013-security-api-contract-correction共同固定 0.13.0 的 API 声明、实现分支和 README 示例。 - 本页综合: 将算法选择、显式签名、验签 key 类型和 claims builder 组织成迁移与排障顺序。
- Java 线上性能排障:从症状到证据的最小决策树
来源与关联文档
研究记录
- 原始文档: src/content/note/zh-cn/jjwt-013-security-api.md
- 来源版本: f49953fb0d4bd52a39bf81167b0c4b7dd7d78a64
- 证据状态: 有效