环境Spring Boot 3.5.16 / Java 21 / jjwt 0.9.1场景在单元测试中签发 JWT连续踩中两个运行时异常。一、背景项目使用 jjwtio.jsonwebtoken:jjwt0.9.1 生成 JWT。一段最简单的签发代码StringtokenJwts.builder().signWith(SignatureAlgorithm.HS256,123456).addClaims(claims).setExpiration(newDate(System.currentTimeMillis()30*60*1000)).compact();在 Java 21 下运行先后抛出两个错误。下面逐一拆解。二、报错一密钥算法与签名方式不匹配异常信息java.lang.IllegalArgumentException: Base64-encoded key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead. at io.jsonwebtoken.impl.DefaultJwtBuilder.signWith(DefaultJwtBuilder.java:98)原因jjwt 的signWith(SignatureAlgorithm, String)这个重载只对 HMAC 算法HS256/HS384/HS512有效——它会把字符串当作 base64 编码的密钥处理。而ES256Elliptic Curve椭圆曲线和RS256RSA属于非对称算法只能通过signWith(SignatureAlgorithm, Key)传入Key对象不能传字符串。修复如果你只是想用字符串密钥那本质上就是 HMAC 用法把算法改成HS256并换成长度足够的密钥即可// HS256 要求密钥至少 32 字节base64 编码StringsecretBase64.getEncoder().encodeToString(12345678901234567890123456789012.getBytes());Jwts.builder().signWith(SignatureAlgorithm.HS256,secret)...如果你确实要用 ES256/RSA需用KeyPairGenerator生成密钥对调用signWith(alg, privateKey)。三、报错二JDK 11 缺失 JAXB真正的“硬骨头”异常信息java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter Caused by: java.lang.ClassNotFoundException: javax.xml.bind.DatatypeConverter原因javax.xml.bindJAXB在 JDK 8 里是标准库的一部分但从JDK 11 起被正式移除JEP 320。jjwt 0.9.1 的Base64Codec依赖javax.xml.bind.DatatypeConverter做所有 Base64 编解码——不仅是密钥解码连compact()编码 JWT 的 header/payload 也会用到它。因此只要在 JDK 11 上运行 0.9.1就一定会在某一步触发这个NoClassDefFoundError。尝试过的“最小改动”及其失败原因一开始我们尝试绕过它改signWith(alg, byte[])重载用byte[]代替String确实避开了密钥解码那一次DatatypeConverter调用。结果仍然报错只是报错位置后移到了compact()→Base64UrlCodec.encode→Base64Codec.encode。结论jjwt 0.9.1 的全部 Base64 操作都走 JAXB单靠改调用方式无法根治必须把 JAXB 本身补回运行时。四、解决方案根据是否升级 jjwt有两条路线。方案 A保留 0.9.1补回 JAXB 依赖改动最小在pom.xml中增加两个依赖提供DatatypeConverter的实现dependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt/artifactIdversion0.9.1/version/dependency!-- jjwt 0.9.1 依赖 JAXB 的 DatatypeConverterJDK 11 已移除需手动补回 --dependencygroupIdjavax.xml.bind/groupIdartifactIdjaxb-api/artifactIdversion2.3.1/version/dependencydependencygroupIdcom.sun.xml.bind/groupIdartifactIdjaxb-impl/artifactIdversion2.3.1/versionscoperuntime/scope/dependency刷新 Maven 后测试即通过。jaxb-api提供类定义jaxb-impl提供运行时实现两者缺一不可。最终可用的测试代码保留 0.9.1 写法TestpublicvoidtestGenerateToken(){MapString,ObjectclaimsnewHashMap();claims.put(username,jinyong);claims.put(password,123456);StringtokenJwts.builder().signWith(SignatureAlgorithm.HS256,YW5jaGFvMTIz).addClaims(claims).setExpiration(newDate(System.currentTimeMillis()30*60*1000)).compact();System.out.println(token);}方案 B升级到 jjwt 0.11.x推荐根治0.11.x 重写了编解码层不再依赖 JAXBAPI 也更规范。依赖改为三件套dependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt-api/artifactIdversion0.11.5/version/dependencydependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt-impl/artifactIdversion0.11.5/versionscoperuntime/scope/dependencydependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt-jackson/artifactIdversion0.11.5/versionscoperuntime/scope/dependency0.11.x 的signWith参数顺序变了且 HS256 需要SecretKeyimportio.jsonwebtoken.security.Keys;importjavax.crypto.SecretKey;SecretKeykeyKeys.hmacShaKeyFor(12345678901234567890123456789012.getBytes());StringtokenJwts.builder().signWith(key,SignatureAlgorithm.HS256)// 注意Key 在前.addClaims(claims).setExpiration(newDate(System.currentTimeMillis()30*60*1000)).compact();校验侧也需同步Jwts.parser().setSigningKey(key).build().parseClaimsJws(token)0.11.x 多了build()。五、最佳实践小结项建议算法与密钥匹配字符串密钥 → 用 HMACHS256非对称 → 用Key对象密钥长度HS256 至少 32 字节不要用123456这种短串做生产密钥JDK 11 用 jjwt 0.9.1必须补jaxb-apijaxb-impl否则NoClassDefFoundError治本方案升级到 jjwt 0.11.x彻底摆脱 JAXB 依赖核心认知NoClassDefFoundError: javax.xml.bind.DatatypeConverter不是代码写错而是“老库 新 JDK”的典型组合问题。改调用方式只能推迟报错真正要补的是被 JDK 移除的运行时模块从架构角度看升级库才是正解。