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.

JSON Web令牌(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,以JSON对象的形式在各方之间安全地传输信息。由于该信息是数字签名的,因此可以对其进行验证和信任。
​ ------[摘自官网]

  • jwt的结构

    # 令牌构成
    	1. Header
    	2. Payload
    	3. Signature
        
        jwt令牌由以上3部分组成-逗号分割(Header.Payload.Signature),其中header和payload使用了Base64编码。
        header: 由两部分组成:alg/typ (typ-令牌的类型,alg-签名算法), 一般header部分默认即可。
        payload: 包含了用户等相关的信息。可以自定义根据需要添加相关数据
        signature: 包含了header, payload的Base64编码结果 和 secret-密钥
    
    如下图所示:
    

image-20211010203625322

jjwt包介绍

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

java-utils封装

import io.jsonwebtoken.*;
import java.util.Date;
import java.util.Map;
import static io.jsonwebtoken.Jwts.builder;

/**
 * Created with IntelliJ IDEA.
 *
 * @author: HP
 * Date: 2021/10/11
 * Time: 15:40
 * Description: 基于jjwt分装的jwt工具类
 */
public class JwtUtil {
    // 可以将其写入配置文件中,在这里直接读取
    private static final String SECRET = "@@#!@#@!$!@";
    private static final String ISSUER = "paul";

    /**
     * 生成jws
     * @param subject
     * @param claims
     * @param expiration 有效时间 单位-分钟
     * @return
     */
    public static String generateJws(String subject, Map<String, Object> claims, Integer expiration) {
        SignatureAlgorithm hs256 = SignatureAlgorithm.HS256;

        JwtBuilder jwtBuilder = builder();  // 获取 JwtBuilder 实例
        if (claims != null && !claims.isEmpty()) {
            jwtBuilder.setClaims(claims);
        }
        if (expiration>0){
            long curr = System.currentTimeMillis();
            Date date = new Date(curr + expiration * 1000 * 60);
            jwtBuilder.setExpiration(date);
        }
        jwtBuilder.setHeaderParam("typ", "JWT")  // 设置header的type
                .setSubject(subject) // JWT的主体(一般为用户ID)
                .setIssuer(ISSUER) // 签发人
                .signWith(hs256, SECRET.getBytes());// 设置密钥
        return jwtBuilder.compact();
    }

    /**
     * 获取数据声明
     * @param token
     * @return
     */
    public static Claims getClaims(String token) {
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(SECRET.getBytes()).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        return claims;
    }
    /**
     * 合法性校验
     * @param token
     * @return
     * true: 合法
     */
    public static Boolean validity(String token){
        Claims claims = getClaims(token);
        return claims != null;
    }

    /**
     * 是否过期
     * @param token
     * @return
     *  (false : 未过期)
     */
    public static Boolean isExpired(String token){
        Claims claims = getClaims(token);
        return !claims.getExpiration().before(new Date());
    }

    /**
     * 获取剩余有效时间
     * @param token
     * @return
     */
    public static Long getRemainTime(String token){
        if (isExpired(token)){
            Claims claims = getClaims(token);
            Date expiration = claims.getExpiration();
            return expiration.getTime() - System.currentTimeMillis();
        }
        return -1L;
    }

    /**
     * 获取用户id (获取subject)
     * @return
     */
    public static String getSubject(String token){
        Claims claims = getClaims(token);
        return claims.getSubject();
    }

    /**
     * 获取其他claims字段
     * @return
     */
    public static Object get(String token,Object key){
        Claims claims = getClaims(token);
        return claims.get(key);
    }
}

常见异常

  • io.jsonwebtoken.MalformedJwtException

    io.jsonwebtoken.MalformedJwtException: Unable to read JSON value: {"typ��)]P��������!L��؉

    异常原因: token非法 (可以在https://jwt.io/#debugger-io中测试一下)

  • io.jsonwebtoken.SignatureException

    io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.

    异常原因:token签名异常 - 签发token时的签名(signWith)和解析时的签名(setSigningKey)的不一致

常见的问题

签名的作用

  • Base64编码的header 和 payload + secret(密钥) 然后将其结果用header中指定的签名算法(默认HS256) 进行签名,保证JWT没有被篡改过。 (签名不能泄露出去)。
  • 注意: jwt的没有任何安全性可言(Base64很容易解密),故payload不要存放用户敏感信息。而,签名(secret)的作用是防止第三方(黑客)拦截请求篡改jwt,篡改后的jwt无法通过校验。

注意事项

  • setClaims (jjwt)

    @Test
    public void testSetClaims(){
        JwtBuilder builder = builder()
            .signWith(SignatureAlgorithm.HS256,"@@!@#".getBytes())
            .setSubject("15e154-515a1-12wda");
        HashMap<String, Object> claims = new HashMap<>();
        claims.put("username", "小明");
        claims.put("avator", "www.xxx.xxx");
        String jws = builder.setClaims(claims).compact();
        System.out.println(jws); 
    }
    /*
    /jws: eyJhbGciOiJIUzI1NiJ9.eyJhdmF0b3IiOiJ3d3cueHh4Lnh4eCIsInVzZXJuYW1lIjoi5bCP5piOIn0.kjNcwOm8e44hOLW6jSGiInr2pLqTwIbPa6TCuQ7WRyg
    */
    
    //解析结果: 其中前面的claims中没有 subject
    

    image-20211011144142872

    NOTE: Calling setClaims will overwrite any existing claim name/value pairs with the same names that might have already been set. ----[摘自官网]

    意思就是:调用setClaims方法会覆盖所有的payload(清空claims)

    // 关键源码
    public JwtBuilder setClaims(Map<String, Object> claims) {
        // 直接改变了claims的指向(相当于即清空了原来的claims, 创建了指定的claims)
        this.claims = Jwts.claims(claims);
        return this;
    }
    
    public static Claims claims(Map<String, Object> claims) {
        return new DefaultClaims(claims);
    }
    

    使用时,要注意setClaims方法的调用位置

  • setExpiration

    • 问题描述: setExpiration 无效 (最终生成的jws中没有exp)

    • 原因:setExpirationsetClaims 的顺序错误

      • 详细分析

        // 相关源码 分析:
        // 显然在ensureClaims中,如果先setExpiration 那么会创建一个新的claims对象并设置expiration,而在后面调用setClaims时会清除掉expiration。导致setExpiration失效
        
        public JwtBuilder setExpiration(Date exp) {
            if (exp != null) {
                this.ensureClaims().setExpiration(exp);
            } else if (this.claims != null) {
                this.claims.setExpiration(exp);
            }
        
            return this;
        }
        
        public Claims setExpiration(Date exp) {
            this.setDate("exp", exp);
            return this;
        }
        
        protected Claims ensureClaims() {
            if (this.claims == null) {
                this.claims = new DefaultClaims();
            }
            return this.claims;
        }
        
        
    • 示例

      • 错误实例

        public static String generateJws(String subject, Map<String, Object> claims, Integer expiration) {
            SignatureAlgorithm hs256 = SignatureAlgorithm.HS256;
        
            JwtBuilder jwtBuilder = builder();  // 获取 JwtBuilder 实例
            if (expiration>0){
                long curr = System.currentTimeMillis();
                Date date = new Date(curr + expiration * 1000 * 60);
                jwtBuilder.setExpiration(date);
            }
        
            if (claims != null && !claims.isEmpty()) {
                jwtBuilder.setClaims(claims);
            }
            jwtBuilder.setHeaderParam("typ", "JWT")  // 设置header的type
                .setSubject(subject) // JWT的主体(一般为用户ID)
                .setIssuer(ISSUER) // 签发人
                .signWith(hs256, SECRET.getBytes());// 设置密钥
            return jwtBuilder.compact();
        }
        
      • 正确实例

        	// ....
            JwtBuilder jwtBuilder = builder();  // 获取 JwtBuilder 实例
        
        	// 先设置claims
            if (claims != null && !claims.isEmpty()) {
                jwtBuilder.setClaims(claims);
            }
        
            // 后设置expiration
            if (expiration>0){
                long curr = System.currentTimeMillis();
                Date date = new Date(curr + expiration * 1000 * 60);
                jwtBuilder.setExpiration(date);
            }
            // .....
        


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM