JWT

把书本读薄,简单介绍一下JWT所具备的特征,最好提前了解 “编码”、“摘要” 和 “加密” 的区别。

JWT简介

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

JWT是一种设计令牌的开放标准。(开发标准,就意味着可以有多种实现)

JWT结构

JWT分为3段,用“.”连接,其中:“头部”和“载荷”通过Base64进行编码的,“签名”是将前2段连接在一起,通过加密算法得出的校验码。

HEADER(头部).PAYLOAD(载荷).SIGNATURE(签名)
头部

Base64解码后是一段JSON,用于说明jwt所采用的算法

alg: HS256
typ: JWT
载荷

Base64解码后是一段JSON,用于存储jwt的详细信息,字段只是推荐,并没有强制要求这些字段必须存在

iss:发行人
exp:token的到期时间
sub:主题
aud:用户
nbf:token在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
签名

“头部” 和 “载荷” 使用Base64编码,编码并不是加密,用户可以随时解码,查看里面的内容。

允许用户看 token 的内容,又不允许他们修改,这就是 “签名” 的作用:防止用户伪造token。

比如:token 过期了,用户偷偷修改了 token 的有效期,这时候,服务器只要把数据再加密一遍,如果加密的结果不一致,就说明token被修改过。

(这里说的加密,可以是摘要算法,也可以使加密算法)

DEMO

自带的加密算法有这么多:none、HS256、HS384、HS512、RS256、RS384、RS512、ES256、ES384、ES512、PS256、PS384、PS512。

挑2个展示效果,对称加密HS256,非对称加密RS256。

Maven依赖

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.17.0</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

HS256

对称加密,加密和解密需要相同的密钥。

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import javax.crypto.SecretKey;
import java.util.Base64;
import java.util.Date;

/**
 * @author Mr.css
 * @date 2022-01-05 15:03
 */
public class JwtHS256 {

//    /**
//     * 生成密钥
//     *
//     * @param key 长度必须是32的倍数
//     * @return SecretKey
//     * @throws IllegalArgumentException -
//     */
//    public static SecretKey generateSecretKey(byte[] key) {
//        if (key.length % 32 != 0) {
//            throw new IllegalArgumentException("key size must be a multiple of 32");
//        }
//        return new SecretKeySpec(key, "AES");
//    }

    /**
     * 密钥,更复杂地,应该将密钥交由数据库管理
     */
    private static SecretKey secretKey = AES.generateSecretKey(MD5.hexBit32("this is a secret key!").getBytes());

    /**
     * 生成jwt
     *
     * @param id        ID
     * @param subject   主题
     * @param ttlMillis 超时时间
     * @return jwt
     */
    public static String encrypt(String id, String subject, long ttlMillis) {
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        JwtBuilder builder = Jwts.builder();
        //ID
        builder.setId(id);
        //主题
        builder.setSubject(subject);
        // 签发者
        builder.setIssuer("system");
        // 签发时间
        builder.setIssuedAt(now);
        // 签名算法以及密匙
        builder.signWith(SignatureAlgorithm.HS256, secretKey);
        // 设置超时时间
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date expDate = new Date(expMillis);
            builder.setExpiration(expDate);
        }
        return builder.compact();
    }

    /**
     * 解析JWT字符串
     *
     * @param jwt jwt
     * @return Claims
     */
    public static Claims decrypt(String jwt) {
        return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();
    }

    public static void main(String[] args) throws InterruptedException {
        //解密
        String jwt = encrypt("id", "sub", 3000);
        System.out.println(jwt);

        //解密
        Claims claims = decrypt(jwt);
        System.out.println(claims);

        //解析头部
        System.out.println(
                new String(Base64.getDecoder().decode("eyJhbGciOiJIUzI1NiJ9")));
        //解析载荷
        System.out.println(
                new String(Base64.getDecoder().decode("eyJqdGkiOiJpZCIsInN1YiI6InN1YiIsImlzcyI6InN5c3RlbSIsImlhdCI6MTY0MTM3NTc3MywiZXhwIjoxNjQxMzc1Nzc2fQ")));

        //5秒之后token失效
        Thread.sleep(5000);
        claims = decrypt(jwt);
        System.out.println(claims);
    }
}

RS256

代码上与HS256基本一致。JWT限定了只能用私钥加密,公钥解密。

什么情况下使用RS256?

令牌由其它服务器发出,我们的服务器只负责调用,我们在使用令牌之前,需要验证令牌是可信任的。

(比如:微服务,一个服务器只负责造令牌,其它负责用,使用之前,需要保证令牌是自己人发出来的。)

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.security.*;
import java.util.Date;

/**
 * @author Mr.css
 * @date 2022-01-05 15:03
 */
public class JwtRS256 {

    /**
     * 公钥/私钥一般交由数据库管理
     */
    private static PublicKey publicKey;
    private static PrivateKey privateKey;


//    /**
//     * 获取密钥对
//     *
//     * @param initialize 长度,1024
//     * @return 密钥对
//     */
//    public static KeyPair getKeyPair(int initialize) throws GeneralSecurityException {
//        KeyPairGenerator keyPairGen;
//        keyPairGen = KeyPairGenerator.getInstance("RSA");
//        keyPairGen.initialize(initialize);
//        return keyPairGen.generateKeyPair();
//    }

    static {
        try {
            KeyPair keyPair = RSA.getKeyPair();
            publicKey = keyPair.getPublic();
            privateKey = keyPair.getPrivate();
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        }
    }

    /**
     * 生成jwt
     *
     * @param id        ID
     * @param subject   主题
     * @param ttlMillis 超时时间
     * @return jwt
     */
    public static String encrypt(String id, String subject, long ttlMillis) {
        Date now = new Date();
        JwtBuilder builder = Jwts.builder();
        //ID
        builder.setId(id);
        //主题
        builder.setSubject(subject);
        // 签发者
        builder.setIssuer("system");
        // 签发时间
        builder.setIssuedAt(now);
        // 签名算法以及密匙
        builder.signWith(SignatureAlgorithm.RS256, privateKey);
        // 设置超时时间
        if (ttlMillis >= 0) {
            long expMillis = now.getTime() + ttlMillis;
            Date expDate = new Date(expMillis);
            builder.setExpiration(expDate);
        }
        return builder.compact();
    }

    /**
     * 解析JWT字符串
     *
     * @param jwt jwt
     * @return Claims
     */
    public static Claims decrypt(String jwt) {
        return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(jwt).getBody();
    }

    public static void main(String[] args) throws InterruptedException {
        //加密
        String jwt = encrypt("id", "sub", 3000);
        System.out.println(jwt);

        //解密
        Claims claims = decrypt(jwt);
        System.out.println(claims);

        //暂停5秒再解密,超时报错
        Thread.sleep(5000);
        claims = decrypt(jwt);
        System.out.println(claims);
    }
}

公钥/私钥——字符串和对象的转换

    /**
     * 获取公钥字符串
     */
    public static String getPublicKey(KeyPair keyPair) {
        return Base64.encodeString(keyPair.getPublic().getEncoded());
    }

    /**
     * 获取私钥字符串
     */
    public static String getPrivateKey(KeyPair keyPair) {
        return Base64.encodeString(keyPair.getPrivate().getEncoded());
    }

    /**
     * 获取公钥对象
     *
     * @param publicKey 公钥字符串
     * @return 公钥
     * @throws GeneralSecurityException -
     */
    public static PublicKey generatePublicKey(String publicKey) throws GeneralSecurityException {
        byte[] keyBytes = Base64.decodeString(publicKey);
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePublic(x509EncodedKeySpec);
    }

    /**
     * 获取私钥对象
     *
     * @param privateKey 私钥字符串
     * @return 私钥
     * @throws GeneralSecurityException -
     */
    public static PrivateKey generatePrivateKey(String privateKey) throws GeneralSecurityException {
        byte[] keyBytes = Base64.decodeString(privateKey);
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePrivate(pkcs8EncodedKeySpec);
    }

posted on 2022-01-06 11:43  疯狂的妞妞  阅读(105)  评论(0编辑  收藏  举报

导航