JJWT解析JWT的JUnit测试报NoSuchMethodError异常排查
问题:调用JJWT的parserBuilder()方法抛出NoSuchMethodError
问题场景
基于JJWT 0.11.5版本实现JWT有效性校验逻辑,核心逻辑是读取配置密钥后调用Jwts.parserBuilder()构建解析器解析令牌,捕获解析抛出的JwtException后封装为401异常返回。
对应实现代码、单元测试、依赖配置如下:
JWT校验方法
public void validateExpiryDate(String token) { try { byte[] secret = configProperties.getTokenSecret().getBytes(StandardCharsets.UTF_8); SecretKey hmacKey = new SecretKeySpec(secret, HMAC_SHA_256_ALGORITHM); Jwts.parserBuilder() .setSigningKey(hmacKey) .build() .parseClaimsJws(token); } catch (JwtException e) { throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, INVALID_TOKEN_ERR); } }
过期场景单元测试
@Test void validatorShouldThrow_when_tokenIsExpired() { Date issuedAt = Date.from(Instant.now()); Date expiresAt = Date.from(issuedAt.toInstant().minus(10, ChronoUnit.MINUTES)); String jwt = generator.createJWT(JWT_TEST_NAME, JWT_TEST_MAIL, JWT_TEST_SUBJECT, JWT_TEST_ID, issuedAt, expiresAt, CONFIG_PROPERTIES_SECRET); assertThrows(ResponseStatusException.class, () -> validator.validateExpiryDate(jwt)); }
测试用JWT生成工具方法
public String createJWT(String name, String email, String subject, String id, Date issuedAt, Date expiresAt, String secret) { return Jwts.builder() .claim("name", name) .claim("email", email) .setSubject(subject) .setId(id) .setIssuedAt(issuedAt) .setExpiration(expiresAt) .signWith(SignatureAlgorithm.HS256, Base64.getDecoder().decode(secret)) .compact(); }
Gradle依赖配置(Kotlin DSL)
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5") implementation("io.jsonwebtoken:jjwt-api:0.11.5") runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
运行测试时未得到预期的ResponseStatusException,反而抛出如下错误:
org.opentest4j.AssertionFailedError: Unexpected exception type thrown ==> expected: <org.springframework.web.server.ResponseStatusException> but was: <java.lang.NoSuchMethodError> Caused by: java.lang.NoSuchMethodError: io.jsonwebtoken.Jwts.parserBuilder()Lio/jsonwebtoken/JwtParserBuilder;
已经通过gradle -q dependencies确认所有显式声明的JJWT依赖版本为0.11.5,无旧版本依赖混入,且清空了本地Maven仓库C:\Users\<user>\.m2\repository后问题仍复现。
根因
Jwts.parserBuilder()是JJWT 0.10.0及以上版本才提供的API,0.9.x及更早版本使用Jwts.parser()作为解析入口。出现该方法不存在的错误,核心原因是运行时实际加载的io.jsonwebtoken.Jwts类来自0.9.x及更早版本的JJWT包,和编译时使用的0.11.5版本API不兼容。
之前的排查存在两个关键遗漏:
- 缓存清理位置错误:Gradle默认不使用
.m2目录下的Maven缓存,自身的依赖缓存存放在C:\Users\<user>\.gradle\caches路径下,清空Maven缓存不会影响Gradle已拉取的依赖文件。 - 依赖作用域配置问题:
runtimeOnly配置的依赖仅在主源码集运行时加入类路径,部分Gradle版本、IDEA测试运行器不会将该作用域的依赖正确加入测试类路径,导致运行测试时JVM优先加载了其他路径下的旧版JJWT类(比如Spring Boot默认依赖管理引入的旧版JJWT、其他第三方库传递引入的0.9.x版本JJWT)。
解决步骤
按以下顺序操作即可修复问题:
- 调整JJWT依赖作用域,将
jjwt-impl和jjwt-jackson从runtimeOnly改为implementation,保证编译、测试、运行全阶段都能加载正确版本的类,修改后配置如下:
implementation("io.jsonwebtoken:jjwt-api:0.11.5") implementation("io.jsonwebtoken:jjwt-impl:0.11.5") implementation("io.jsonwebtoken:jjwt-jackson:0.11.5")
- 强制刷新Gradle依赖,执行以下命令清理构建缓存并重新拉取依赖:
gradle clean build --refresh-dependencies
如果命令执行后仍有问题,手动删除C:\Users\<user>\.gradle\caches下所有jjwt相关的缓存文件夹即可。
- 在IDEA的Gradle面板点击刷新按钮,重新同步项目依赖。
- 修复隐藏的逻辑问题:当前JWT生成和校验的密钥处理逻辑不一致——生成JWT时对传入的secret做了Base64解码,校验时却直接取secret字符串的UTF-8字节数组作为密钥,依赖问题解决后会触发签名无效异常,需要将两边的密钥处理逻辑统一。
内容的提问来源于stack exchange,提问作者Hristo Naydenov
相关产品推荐
相关产品推荐

