天天看点

【JWT】JWT 实现用户登录控制一、JWT 简介二、JWT 的构成三、工具类代码

目录

一、JWT 简介

二、JWT 的构成

2.1 头部(Header)

2.2 载荷 (Payload)

2.3 签名

2.4 最后生成出的 jwt 

2.5 测试和验证

三、工具类代码

一、JWT 简介

JSON Web Token(JWT) 是一种十分轻巧的规范,这个规范允许我们使用 JWT 在用户和服务器之间传递安全可靠的信息。

即 JWT 是用于在客户端和服务器之间传递用户信息的一段 JSON 格式的加密字符串,同时 JWT 可以用于让各个微服务识别用户的身份信息。即 JWT 中封装有用户的身份信息。

二、JWT 的构成

一个 JWT 实际上就是一个字符串,它由三部分组成:头部、载荷和签名

2.1 头部(Header)

头部用于描述关于该 JWT 的最基本信息,例如它的类型以及签名所用的算法等,这一部分被可以被表示成一个 JSON 对象。

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

这段 JSON 字符串指明了签名算法是 HS256 算法,实际使用中这段字符串会被 base64编码,被编码后的字符串为:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
           

转换地址:http://base64.xpcha.com/

2.2 载荷 (Payload)

载荷就是存放有效信息的地方

假设真实内容为:

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

base64 加密后:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
           

标准中的注册声明(建议但不强制使用):

声明名称 作用
iss jwt 的签发者
sub 当前令牌的描述说明
aud 接收 jwt 的一方
exp jwt 的过期时间,这个时间必须大于签发时间
nbf 定义在什么时间之前,这个 jwt 是不可用的
iat jwt 的签发时间
jti jwt 的唯一身份标识,主要用来作为一次性 token,避免重放攻击

公共声明:

公共的声明可以添加任何信息,一般添加用户的相关信息或其他业务需要的必要信息,但由于这部分信息在客户端可以被解密,设置这部分可以不加密,故不建议添加敏感信息。这部分信息不参与令牌校验。

私有声明:

私有声明是提供者和消费者共同定义的声明,由于 base64是对称解密,即这部分信息可以被解密,故不建议存放敏感信息。这部分信息不参与令牌校验。

一个载荷的构成:

(标准中注册的声明 + 公共的声明 + 私有声明 ) => base64 加密 => 一个载荷

2.3 签名

jwt 的第三部分是一个签证信息,用于校验数据是否被篡改。签证信息由三部分组成:

  • header(base64后的)
  • payload(base64后的)
  • secret (秘钥,或称盐)

签名的生成方式是:

Base64(Header) . Base64(Payload) + 秘钥(盐) => 加密(使用 Header 中指定的算法加密) => 密文(签名)

例如(数据不准确):

Base64(Header)为:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Base64(Payload)为:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

secret为: abcdefg

则签名为: HS256(eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.abcdefg) => TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

注意:

secret 是保存在服务器端的, jwt 的签发也是发生在服务器端,可以认为 secret 就是服务器端用于签名的私钥,在任何场景中都不应该泄漏出去,一旦客户端得知这个 secret,就意味着客户端可以自我签发 jwt 了。

签名的作用:

用于校验令牌是否被篡改

由于 secret 是保存在服务器的,若认为篡改了 jwt,则签名部分肯定发生了变化,服务器只要对比签名部分就能够判断出 jwt 是否被篡改。

2.4 最后生成出的 jwt 

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7Hg

Q

2.5 测试和验证

/**
 * @File: JwtTest
 * @Description: 令牌生成和解析测试
 * @Author: tom
 **/
@Slf4j
public class JwtTest {

    private String signKey = "tomTest";

    /**
     * 创建令牌
     */
    @Test
    public void testCreateToken() throws InterruptedException {
        // 构建jwt令牌对象
        JwtBuilder builder = Jwts.builder();
        builder.setIssuer("tom"); // 颁发者
        builder.setIssuedAt(new Date()); // 颁发日期
        builder.setExpiration(new Date(System.currentTimeMillis() + 20000)); // 设置定时,2秒过期
        builder.setSubject("jwt 令牌测试"); // 主题测试
        builder.signWith(SignatureAlgorithm.HS256, signKey); // 1. 签名算法 2. 秘钥
        // 自定义载荷信息
        Map<String, Object> userInfo = new HashMap<String,Object>();
        userInfo.put("company", "dis");
        userInfo.put("address", "北京");
        userInfo.put("money", 3500);
        // 添加载荷
        builder.addClaims(userInfo);

        // 获取生成 token
        String token = builder.compact();
        System.out.println(token);

//        Thread.sleep(3000);
        parseToken(token);
    }

    /**
     * 令牌解析
     */
    public void parseToken(String token) {
        Claims claims = Jwts.parser().setSigningKey(signKey).parseClaimsJws(token).getBody();
        System.out.println(claims.toString());
    }
}
           

三、工具类代码

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;

/**
 * JWT工具类
 */
public class JwtUtil {

    // 有效期为
    public static final Long JWT_TTL = 3600000L;// 60 * 60 *1000  一个小时
    // 设置秘钥明文
    public static final String JWT_KEY = "tomcast";
    // 设置颁发者
    public static final String JWT_ISS = "tom";

    /**
     * 创建token
     * @param id
     * @param subject
     * @param ttlMillis
     * @return
     */
    public static String createJWT(String id, String subject, Long ttlMillis) {

        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if(ttlMillis==null){
            ttlMillis=JwtUtil.JWT_TTL;
        }
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        SecretKey secretKey = generalKey();

        JwtBuilder builder = Jwts.builder()
                .setId(id)              //唯一的ID
                .setSubject(subject)   // 主题  可以是JSON数据
                .setIssuer(JWT_ISS)     // 签发者
                .setIssuedAt(now)      // 签发时间
                .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
                .setExpiration(expDate);// 设置过期时间
        return builder.compact();
    }

    /**
     * 生成加密后的秘钥 secretKey
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }

    /**
     * 解析令牌数据
     * @param jwt
     * @return
     */
    public static Claims parseJWT(String jwt) {
        SecretKey secretKey = generalKey();
        return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();
    }
}