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