目錄
一、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
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();
}
}