JWT 构成

JWT 是一个很长的字符串,看起来如下所示,中间用“.”隔开分为三部分。

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJjdXN0b20gc3ViamVjdCIsImlzcyI6InpoYW5nc2FuIiwiYXVkIjoibm9ybWFsIHVzZXIiLCJuYmYiOjE2NjYxNzAwMjEsImlhdCI6MTY2NjE2OTk5MSwiZXhwIjoxNjY2MTcwMDUxfQ.QgGH1zo4FXOmG4mIrZDRQ-KaROgLEuKjlL78bGSRg0U

其中分为三部分为“Header”、“Payload”、“Signature”。

头(Header):eyJhbGciOiJIUzI1NiJ9,是一个 json 对象,描述 JWT 的元数据,包含所使用的签名算法等等。使用 Base64 算法将数据进行加密。可使用 Base64 解密工具解密,解密之后的数据如下。

{"alg":"HS256"}

Payload

负载(Payload):也是一个 JSON 对象,使用 Base64 算法将数据进行加密。

eyJzdWIiOiJjdXN0b20gc3ViamVjdCIsImlzcyI6InpoYW5nc2FuIiwiYXVkIjoibm9ybWFsIHVzZXIiLCJuYmYiOjE2NjYxNzAwMjEsImlhdCI6MTY2NjE2OTk5MSwiZXhwIjoxNjY2MTcwMDUxfQ

解密之后的数据

{"sub":"custom subject","iss":"zhangsan","aud":"normal user","nbf":1666170021,"iat":1666169991,"exp":1666170051}

解密后的 JSON 字串中,有如下官方的字段,且均为可选

  • exp(expire):过期时间
  • sub(subject):主题
  • iss(issuer):签发人
  • aud(audience):受众
  • nbf(not before):生效时间
  • iat(issued at):签发时间
  • jti(JWT ID):编号

基于 Java 实现的 JJWT,过期时间和生效时间可以设定,在特定的情况下会抛出 ExpiredJwtExceptionPrematureJwtException 异常。

Signature

签名(Signature):8P1Gnfp1BYL_ViewC_yk1vwsjqF__-UagQDvPJADV9o,签名可防止数据被篡改。

签名算法如下

method(base64(header) + "." + base64(payload) + "." + secret)

其中 method 代表的算法名称,可用的算法有很多,在 JJWT 中可使用的签名算法都放在 SignatureAlgorithm 类中,默认使用的签名算法为 HMAC-SHA256。

secret:密钥必须在服务器中保管好,不能随意发给用户。我们使用 JJWT 的 secret 生成器来生成密钥,当然自己来生成也是可以的。使用生成器生成的密钥如下。

SecretKey key = MacProvider.generateKey(SignatureAlgorithm.HS256);

特点与用途

主要用于身份认证和信息交换。

优点:不需经过数据库校验

缺点:

  • 一旦签发,只能等到过期才会失效,无法在期间手动终止 token。
  • JWT 是明文的,有敏感数据时必须要将 JWT 加密,传输时尽量使用 HTTPS 协议传输数据。

实践一下

一个简单的控制台程序,引入 Java JWT 的依赖

<!-- jjwt核心 -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.2</version>
</dependency>

<!-- 包含签名算法 -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>

<!-- 处理jwt中的json部分 -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>

示例程序

public class MainApp {

    // 生成的密钥
    static SecretKey key = MacProvider.generateKey(SignatureAlgorithm.HS256);

    static String jwt = "";

    // 加密方法
    public static void enc() {

        LocalDateTime now = LocalDateTime.now().plusSeconds(30);

        Instant instant = now.atZone(ZoneId.systemDefault()).toInstant();
        Date nb = Date.from(instant); // 生效时间

        now = now.plusSeconds(30);

        instant = now.atZone(ZoneId.systemDefault()).toInstant();
        Date expire = Date.from(instant); // 失效时间

        String compact = Jwts.builder().serializeToJsonWith(new JacksonSerializer<>())
                .setHeader(new HashMap<String, Object>() {{

                }})
                .setSubject("Joe") // 主题
                .setIssuer("zhangsan") // 设置签发人
                .setSubject("custom subject") // 自定义主题
                .setAudience("normal user") // 设置受众
                .setNotBefore(nb) // 设置生效时间
                .setIssuedAt(new Date()) // 设置签发时间
                .setExpiration(expire) // 设置过期时间
                .signWith(key, SignatureAlgorithm.HS256) // 生成签名
                .compact();

        System.out.println(compact);

        jwt = compact;

    }

    // 解密方法
    public static void dec() {
        JwtParser build = Jwts.parserBuilder()
                .setSigningKey(key)
                .deserializeJsonWith(new JacksonDeserializer<>()).build();

        try {
            // 获取 payload 信息
            Jws<Claims> claimsJws = build.parseClaimsJws(jwt);

            Claims body = claimsJws.getBody();

            System.out.println("主题:" + body.getSubject());
            System.out.println("受众:" + body.getIssuer());

            // 获取生效时间
            Date date = body.getNotBefore();
            String format = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault())
                    .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            System.out.println("生效时间:" + format);

            // 获取签发时间
            date = body.getIssuedAt();
            format = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault())
                    .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            System.out.println("签发时间:" + format);

            // 获取过期时间
            date = body.getExpiration();
            format = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault())
                    .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            System.out.println("过期时间:" + format);


            System.out.println(body.getSubject());
        } catch (ExpiredJwtException e) {
            System.out.println("token到期了,重新签发一下!");
        } catch (PrematureJwtException e) {
            System.out.println("token还未生效,请再等等!");
        }
    }

    // 主程序
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (true) {
            String s = scanner.nextLine();
            if(s.equals("exit")) {
                break;
            }
            if(s.equals("enc")) {
                enc();
            }
            if(s.equals("dec")) {
                dec();
            }
        }

    }

}