Bootstrap

JWT介绍和使用

JWT介绍和使用

JWT介绍

JWT(JSON Web Token)是一个开放的标准(RFC 7519),JWT定义了一种简介的、自包含的协议格式。可以用于在通信的双方传递json对象,传递的信息可以被信任,因为信息是被数字签名的。JWT可以使用HMAC算法或者使用RSA/ECDSA算法的公钥/私钥对签名。

JWT格式

JWT包含三部分内容,Header、Plyload和Signature。

  • Header

    • 包括令牌的类型以及使用的哈希算法;使用base64url编码后就是JWT的第一部分内容
  • Payload

    • Payload的内容是一个json对象,它是存放有效信息的地方,可以存放jwt提供的现成字段,比如:iss(签发者),exp(过期时间戳信) ,sub(面向的用户)等;同时也可以自定义字段存放数据,但是不建议存放敏感数据,因为此部分可以解码还原原始内容;使用base64url编码后就是JWT的第二部分内容
  • Signature

    • Signature是签名,用于防止jwt内容被篡改,这个部分使用base64url将前两部分进行编码,编码后使用“.” 连接组成字符串,最后使用header中声明的签名算法进行签名。

假设现在一个JWT的Header和Payload部分内容如下
Header:

{
  "alg": "HS384",
  "typ": "JWT"
}

payload

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022
}

那么Signature的的内容如下所示,“yguyhdqwodqwddqw1”是秘钥

HMACSHA384(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  yguyhdqwodqwddqw1
)

最后生成的JWT如下:

eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.f7IalcwE38sVPor1FBUwAwso6exiwF80y0nM6bLkAvooNFGjCoJXRL0DMpaQB42C

payload和claims介绍

payload和claims的概念在JJWT开源库中使用了,所以这里简单介绍一下。

前面已经讲过了payload是一个JSON对象,包含了实际传输的数据。而Payload的主要目的是携带与特定用户或请求相关的信息,这些信息就被称为“claims”。

Claims(声明)是Payload中包含的具体键值对数据项,它们用来表述特定的声明或声明集,这些声明描述了与JWT相关的事实。根据其用途和规范要求,claims可以分为以下几类:

  • Reserved Claims(保留声明): 这些是JWT标准中预定义的、具有特殊含义的claims。虽然它们是可选的,但如果使用,应遵循标准规定的名称和格式。常见的保留claims包括:
    • iss(issuer):签发者。
    • sub(subject):主题,标识JWT所代表的个体。
    • aud(audience):接收方,标识预期的JWT接收者。
    • exp(expiration time):过期时间,定义了JWT的有效期截止时间。
    • nbf(not before):生效时间,在此时间之前JWT不应被接受处理。
    • iat(issued at):签发时间,表明JWT的创建时间。
    • jti(JWT ID):唯一标识符,用于防止JWT重放攻击。
  • Public Claims(公共声明)
    • 这些claims没有在JWT标准中明确规定,但可以通过IANA JSON Web Token Registry进行注册,以确保其名称在全球范围内是唯一的,避免不同系统间的冲突。公共claims通常用于满足特定的应用程序或行业需求。
  • Private Claims(私有声明)
    • 开发者可以自由定义的claims,用于承载应用程序所需的任何额外信息。私有claims无需注册,其名称由开发者自行决定,如示例中提到的user_id和user_name。这些声明对JWT的使用者具有特定意义,但对JWT标准本身来说是无特殊含义的。

JWT使用

jjwt是Java Json Web Tokens的缩写,它所示一个用于在Java中实现JWT的开源库。

jjwt依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

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


<!-- javax.xml.bind.DatatypeConverter 类已被弃用并从 Java SE 9 中移除-->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.1</version>
        </dependency>

这是自己写的JJWT工具类

public class JJWTUtils {

    private static final Logger logger = LoggerFactory.getLogger(JJWTUtils.class);

    private static final String SECRET = "mpd';dqw823djq3d902";

    /**
     * 生成token
     */
    public static String getToken(Map<String, Object> map) {
        //设置内容
        JwtBuilder jwtBuilder = Jwts.builder().setClaims(map);
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.SECOND, 100);
        //设置过期时间, 100s后过期
        jwtBuilder.setExpiration(instance.getTime());
        String token = jwtBuilder.signWith(SignatureAlgorithm.HS256, SECRET.getBytes()).compact();
        return token;
    }

    /**
     * 校验token
     * @param token  jwt字符串
     * @return
     */
    public static boolean verifyToken(String token) {
        String[] tokenSplit = token.split(".");
        String header = tokenSplit[0];
        String payload = tokenSplit[1];
        String signature = tokenSplit[2];

        String headerJson = new String(Base64.getDecoder().decode(header));
        String payloadJson = new String(Base64.getDecoder().decode(payload));

        byte[] keyBytes = SECRET.getBytes(StandardCharsets.UTF_8);
        Key signingKey = Keys.hmacShaKeyFor(keyBytes);


        // 验证JWT签名,如果JWT签名无效,这里会抛出SignatureException异常
        boolean result = true;
        try {
            Jwts.parser().setSigningKey(signingKey).parse(token);
        } catch (Exception e) {
            result = false;
        }

        return result;
    }

    /**
     * 获取token中的携带的信息
     */
    public static Map<String, Object> getPayload(String token) {
        //claims是一个Map,存放了通过JwtBuilder设置进去的内容
        Claims claims = Jwts.parser().setSigningKey(SECRET.getBytes())
                .parseClaimsJws(token).getBody();
        return claims;
    }


    public static void main(String[] args) {
        HashMap<String, Object> payload = new HashMap<>();
        payload.put("name", "张三");
        payload.put("id", "1");
        String token = JJWTUtils.getToken(payload);
        logger.info("token: " + token);

        Map<String, Object> payload1 = JJWTUtils.getPayload(token);
        logger.info("payload1: " + payload1);
    }

}

jjwt使用的坑

问题1
java.lang.ClassNotFoundException: javax.xml.bind.DatatypeConverter at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520) … 6 more

解决方法
这个错是由于在Java8以后,javax.xml.bind.DatatypeConverter 类已被弃用并从 Java SE 9 中移除而导致的,所以加入缺少的依赖即可

        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.1</version>
        </dependency>

问题2
ava.lang.IllegalStateException: Both ‘payload’ and ‘claims’ cannot both be specified. Choose either one. at io.jsonwebtoken.impl.DefaultJwtBuilder.compact(DefaultJwtBuilder.java:262) at com.example.sso.utils.JJWTUtils.getToken(JJWTUtils.java:38) at com.example.sso.utils.JJWTUtils.main(JJWTUtils.java:58)

解决方法

不能同时设置payload和claims。

参考

  1. Introduction to JSON Web Tokens
;