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