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-密鑰 如下圖所示:

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.MalformedJwtExceptionio.jsonwebtoken.MalformedJwtException: Unable to read JSON value: {"typ��)]P��������!L��؉
異常原因: token非法 (可以在https://jwt.io/#debugger-io中測試一下)
-
io.jsonwebtoken.SignatureExceptionio.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
NOTE: Calling
setClaimswill 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) -
原因:
setExpiration與setClaims的順序錯誤-
詳細分析
// 相關源碼 分析: // 顯然在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); } // .....
-
-
