JWT

JWT

概述

Json Web Token,通过数字签名的方式,以Json对象为载体,在不同的服务器终端之间安全地传输信息

JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。

服务器应用在接受到JWT后,会首先对头部和载荷的内容用同一算法再次签名,如果服务器应用对头部和载荷再次以同样方法签名之后发现,自己计算出来的签名和接受到的签名不一样,那么就说明这个Token的内容被别人动过的,我们应该拒绝这个Token,返回一个HTTP 401 Unauthorized响应。

组成

​ JWT由三部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.进行连接形成最终传输的字符串

  • Header

    JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存。

    注意:

    HS256 属于对称加密算法,加密和解密公用一个密钥;RS256属于非对称加密,需要通过公钥和密钥来进行加解密。

    {
      'typ':'JWT',
      'alg':'HS256'
    }
    
  • Payload

    有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择。

    iss:发行人

    exp:到期时间

    sub:主题

    aud:用户

    nbf:在此之前不可用

    iat:发布时间

    jti:JWT ID用于标识该JWT

    除以上默认字段外,我们还可以自定义私有字段

    {
      "name":"levi",
      "admin":true
    }
    

    请注意,默认情况下JWT是未加密的,因为只是采用base64算法,拿到JWT字符串后可以转换回原本的JSON数据,任何人都可以解读其内容,因此不要构建隐私信息字段,比如用户的密码一定不能保存到JWT中,以防止信息泄露。JWT只是适合在网络中传输一些非敏感的信息

  • Signature

    签名哈希部分是对上面两部分数据签名,将base64编码后的header和payload数据用.连接起来形成前两段token,而第三段token就是使用base64编码后的header和payload数据通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密钥(secret)。该密码仅仅保存在服务器中,并且不能向用户公开。然后,使用header中指定的签名算法(默认情况下为HMAC SHA256)生成签名。

加密与解密

先导入坐标

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
使用HS256进行加解密(对称加密)
@Test
public void generateAndParseTokenByHs256() {
    // 过期时间
    long time = System.currentTimeMillis() + 60*1000;

    String token = Jwts.builder()
        	//Header
        	.setHeaderParam("typ","JWT")
        	.setHeaderParam("alg","HS256")
            // Payload装载的信息
            .claim("userInfo", "levi")
            // 过期的时间
            .setExpiration(new Date(time))
            // 标识 token 唯一的 ID
            .setId(UUID.randomUUID().toString())
            // 数字签名
            .signWith(SignatureAlgorithm.HS256, "admin")
            .compact();
    // 通过算法和密钥进行加密
    System.out.println(token);

    // 通过密钥进行解密
    Jws<Claims> claimsJws = Jwts.parser().setSigningKey("admin").parseClaimsJws(token);
    Claims body = claimsJws.getBody();
    System.out.println("body.get(\"userInfo\") = " + body.get("userInfo"));
}
使用RS256进行加解密(非对称加密)

因为RS256是非对称加密算法,所以需要先生成一对公钥和密钥

public class GenerateKey {
    public static void main(String[] args) throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(1024);

        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        // 获取公钥和私钥对象
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();

        log.info("private key: [{}]", Base64.encode(privateKey.getEncoded()));
        log.info("public key: [{}]", Base64.encode(publicKey.getEncoded()));
    }
}

将生成的公钥和密钥保存为常量

public static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNA···";
public static final String PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwgg···";

将生成的私钥转换成PrivateKey

/**
 * 获取私钥
 * @return PrivateKey
 * @throws Exception exception
 */
private PrivateKey getPrivateKey() throws Exception {
    PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(
        new BASE64Decoder().decodeBuffer(Constant.PRIVATE_KEY)
    );
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    return keyFactory.generatePrivate(priPKCS8);
}

开始加密(加密时使用PrivateKey

@Test
public void generateTokenByRS256() throws Exception {
    String token = Jwts.builder()
            .claim("username", "levi")
            .setExpiration(new Date(System.currentTimeMillis() + 60 * 1000))
            .setId(UUID.randomUUID().toString())
            .signWith(SignatureAlgorithm.RS256, getPrivateKey())
            .compact();
    // 获取加密的 token
    System.out.println(token);
}

解密前先将公钥转换成PublicKey

/**
 * 根据本地存储的公钥获取到 PublicKey 对象
 *
 * @return PublicKey
 */
private static PublicKey getPublicKey() throws Exception {
    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(
            new BASE64Decoder().decodeBuffer(PUBLIC_KEY)
    );
    return KeyFactory.getInstance("RSA").generatePublic(keySpec);
}

开始解密

@Test
public void parseTokenByRS256() throws Exception {
    String token = Jwts.builder()
            .claim("username", "levi")
            .setExpiration(new Date(System.currentTimeMillis() + 60 * 1000))
            .setId(UUID.randomUUID().toString())
            .signWith(SignatureAlgorithm.RS256, getPrivateKey())
            .compact();
    // 获取加密的 token
    System.out.println(token);

    // 解密 token
    Jws<Claims> claimsJws = Jwts.parser().setSigningKey(getPublicKey()).parseClaimsJws(token);
    Claims body = claimsJws.getBody();
    System.out.println(body.get("username"));
}

鉴权

将用户的权限信息放在JWT的负载中,当请求进入时,拦截请求,获取 token 中的用户信息,并进行相应的权限校验逻辑

posted @   还是做不到吗  阅读(87)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示