掘金 后端 ( ) • 2024-06-18 17:50

背景介绍

在项目开发中,使用第三方库能够大大提高开发效率。然而,随着时间的推移,库的版本不断更新,有时会引入不兼容的变化。我在使用 JJWT(Java JSON Web Token)库时就遇到了这样的情况。

问题描述

我之前一直使用的是 JJWT 0.9.1 版本,这段代码是在本科大三的时候写的。之后每次需要自定义 JJWT 工具时,我都是直接复制这段代码。直到2024年,在一个新的系统中,我遇到了一个 bug,提示无法找到 .SignatureAlgorithm 包。

排查过程

首先,我去 Maven 仓库查看了 JJWT 的坐标,重新安装了 Maven 依赖,但问题依旧存在。这时我决定更新 JJWT 的版本,看看是否能解决问题。

在搜索和阅读了相关资料后,我发现 JJWT 库在较新的版本(0.10.0 及以上)中进行了模块化拆分。具体如下:

  • jjwt-api: 包含所有的 API 接口和核心类(包括 SignatureAlgorithm)。
  • jjwt-impl: 包含 API 的具体实现。
  • jjwt-jackson 或 jjwt-gson: 提供 JSON 序列化和反序列化支持。

在 API 方面也发生了这些变化:

  • SignatureAlgorithm:SignatureAlgorithm 类仍然是核心功能的一部分,但现在包含在 jjwt-api 模块中。
  • ParserBuilder:引入了 parserBuilder() 方法,替代了旧的 parser() 方法,现在构建一个 JwtParserBuilder 实例,增强了 JWT 解析的灵活性和可配置性。

以及在兼容性和使用方面

  • 向后兼容:JJWT 0.10.1 旨在保持向后兼容,但需要更改项目中依赖的包含和使用方式。
  • 安装:项目现在需要在 Maven 或 Gradle 配置中明确包含 jjwt-api、jjwt-impl 和 jjwt-jackson 或 jjwt-gson 模块。

解决方案

了解了这些变化后,我调整了 Maven 依赖,分别导入了 jjwt-apijjwt-impl 和 jjwt-jackson 模块。重新编译项目,发现之前的 SignatureAlgorithm 问题消失了。

调整后的代码如下:

public class JwtUtil {
    /**
     * 生成jwt
     * 使用Hs256算法, 私匙使用固定秘钥
     *
     * @param secretKey jwt秘钥
     * @param ttlMillis jwt过期时间(毫秒)
     * @param claims    设置的信息
     * @return
     */
    public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
        // 指定签名的时候使用的签名算法,也就是header那部分
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        // 生成JWT的时间
        long expMillis = System.currentTimeMillis() + ttlMillis;
        Date exp = new Date(expMillis);

        // 设置jwt的body
        JwtBuilder builder = Jwts.builder()
                // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                // 设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置过期时间
                .setExpiration(exp);

        return builder.compact();
    }

    /**
     * Token解密
     *
     * @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
     * @param token     加密后的token
     * @return
     */
    public static Claims parseJWT(String secretKey, String token) {
        // 得到DefaultJwtParser
        Claims claims = Jwts.parser()
                // 设置签名的秘钥
                .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置需要解析的jwt
                .parseClaimsJws(token).getBody();
        return claims;
    }
}

思考与总结

JJWT 的模块化设计将不同功能分开,使得依赖管理更加清晰。对于仅需要部分功能的项目,减少了不必要的依赖。 在之后做项目的时候尽量在项目的构建工具锁定依赖的版本,或者使用 LTS 版本,会减少很多莫名其妙的 bug。