你好我是辰兮,很高興你能來閱讀,本篇給是初學JWT單點登錄後的困惑,現在源碼學習後更加了解JWT的結構,小結下來,獻給初學者,共同成長,一起進步。
JWT單點登錄案例
:JWT單點登錄代碼實現
一、JWT的結構
JWT的結構是什麼樣的?
token構成包含三個部分:
- Header 頭部
- Payload 負載
- Signature 簽名
//格式如下
xxx.yyy.zzz
①JWT-header(頭信息)
由兩部分組成,令牌類型(即:JWT)、散列算法(HMAC、RSASSA、RSASSA-PSS等)
JWT頭部分是一個描述JWT元數據的JSON對象,通常如下所示。
{
"alg": "HS256",
"typ": "JWT"
}
然後,這個JSON被編碼爲Base64Url,形成JWT的第一部分。
②有效載荷payload
有效載荷部分,是JWT的主體內容部分,也是一個JSON對象,包含需要傳遞的數據。
JWT的第二部分是payload,其中包含claims。claims是關於實體(常用的是用戶信息)和其他數據的聲明
claims有三種類型: registered, public, and private claims。
- Registered claims: 這些是一組預定義的claims,非強制性的,但是推薦使用, iss(發行人), exp(到期時間),sub(主題), aud(觀衆)等;
- Public claims: 自定義claims,注意不要和JWT註冊表中屬性衝突,這裏可以查看JWT註冊表;
- Private claims:這些是自定義的claims,用於在同意使用這些claims的各方之間共享信息,它們既不是Registered claims,也不是Public claims;
{
"sub": "1234567890",
"name": "Tom",
"admin": true
}
請注意,默認情況下JWT是未加密的,任何人都可以解讀其內容,因此不要構建隱私信息字段,存放保密信息,以防止信息泄露。
JSON對象也使用Base64 URL算法轉換爲字符串保存。
③簽名哈希
簽名哈希部分是對上面兩部分數據簽名,通過指定的算法生成哈希,以確保數據不會被篡改。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SfcKxwRJSMeKF2QT4fwpMeJf36POkayJV_adQssw6f
在計算出簽名哈希後,JWT頭,有效載荷和簽名哈希的三個部分組合成一個字符串,每個部分用"."分隔,就構成整個JWT對象。
④Base64URL算法
如前所述,JWT頭和有效載荷序列化的算法都用到了Base64URL。該算法和常見Base64算法類似,稍有差別。
作爲令牌的JWT可以放在URL中(例如api.example/?token=xxx)。 Base64中用的三個字符是"+","/“和”=",由於在URL中有特殊含義,因此Base64URL中對他們做了替換:"=“去掉,”+“用”-“替換,”/“用”_"替換,這就是Base64URL算法。
二、JWT源碼學習
參考一下常見的代碼初學者可能看上去很複雜,接下來分析一波
//登錄成功之後,需要生成token
String token = Jwts.builder().setSubject("用戶名/用戶信息") //主題,可以放用戶的詳細信息
.setIssuedAt(new Date()) //token創建時間
.setExpiration(new Date(System.currentTimeMillis() + 60000)) //token過期時間
.setId("用戶ID") //用戶ID
.setClaims(hashMap) //配置角色信息
.signWith(SignatureAlgorithm.HS256, "WuHan") //加密方式和加密密碼
.compact();
首先Jwts是Java已經被人寫好的一個被final修飾的類,然後裏面自帶很多方法
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package io.jsonwebtoken;
import io.jsonwebtoken.impl.DefaultClaims;
import io.jsonwebtoken.impl.DefaultHeader;
import io.jsonwebtoken.impl.DefaultJwsHeader;
import io.jsonwebtoken.impl.DefaultJwtBuilder;
import io.jsonwebtoken.impl.DefaultJwtParser;
import java.util.Map;
public final class Jwts {
private Jwts() {
}
public static Header header() {
return new DefaultHeader();
}
public static Header header(Map<String, Object> header) {
return new DefaultHeader(header);
}
public static JwsHeader jwsHeader() {
return new DefaultJwsHeader();
}
public static JwsHeader jwsHeader(Map<String, Object> header) {
return new DefaultJwsHeader(header);
}
public static Claims claims() {
return new DefaultClaims();
}
public static Claims claims(Map<String, Object> claims) {
return new DefaultClaims(claims);
}
public static JwtParser parser() {
return new DefaultJwtParser();
}
public static JwtBuilder builder() {
return new DefaultJwtBuilder();
}
}
JwtBuilder是一個接口,裏面也自帶很多方法,每一個方法的返回值類型是自己本身,這裏就是給用戶設置相關信息的,所以代碼你看上去是連着set的。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package io.jsonwebtoken;
import java.security.Key;
import java.util.Date;
import java.util.Map;
public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
JwtBuilder setHeader(Header var1);
JwtBuilder setHeader(Map<String, Object> var1);
JwtBuilder setHeaderParams(Map<String, Object> var1);
JwtBuilder setHeaderParam(String var1, Object var2);
JwtBuilder setPayload(String var1);
JwtBuilder setClaims(Claims var1);
JwtBuilder setClaims(Map<String, Object> var1);
JwtBuilder addClaims(Map<String, Object> var1);
JwtBuilder setIssuer(String var1);
JwtBuilder setSubject(String var1);
JwtBuilder setAudience(String var1);
JwtBuilder setExpiration(Date var1);
JwtBuilder setNotBefore(Date var1);
JwtBuilder setIssuedAt(Date var1);
JwtBuilder setId(String var1);
JwtBuilder claim(String var1, Object var2);
JwtBuilder signWith(SignatureAlgorithm var1, byte[] var2);
JwtBuilder signWith(SignatureAlgorithm var1, String var2);
JwtBuilder signWith(SignatureAlgorithm var1, Key var2);
JwtBuilder compressWith(CompressionCodec var1);
String compact();
}
JWT的解析
try {
JwtParser parser = Jwts.parser();
parser.setSigningKey("WuHan");//解析 要和上面“暗號”一樣
Jws<Claims> claimsJws = parser.parseClaimsJws(token);
Claims body = claimsJws.getBody();
String username = body.getSubject();
// Object role = body.get("role");
return true;
} catch (ExpiredJwtException e) {
e.printStackTrace();
} catch (UnsupportedJwtException e) {
e.printStackTrace();
} catch (MalformedJwtException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
setSigningKey() 與builder中籤名方法signWith()對應,parser中的此方法擁有與signWith()方法相同的三種參數形式,用於設置JWT的簽名key,用戶後面對JWT進行解析。
package io.jsonwebtoken;
import java.security.Key;
import java.util.Date;
public interface JwtParser {
char SEPARATOR_CHAR = '.';
JwtParser requireId(String var1);
JwtParser requireSubject(String var1);
JwtParser requireAudience(String var1);
JwtParser requireIssuer(String var1);
JwtParser requireIssuedAt(Date var1);
JwtParser requireExpiration(Date var1);
JwtParser requireNotBefore(Date var1);
JwtParser require(String var1, Object var2);
JwtParser setClock(Clock var1);
JwtParser setAllowedClockSkewSeconds(long var1);
JwtParser setSigningKey(byte[] var1);
JwtParser setSigningKey(String var1);
JwtParser setSigningKey(Key var1);
JwtParser setSigningKeyResolver(SigningKeyResolver var1);
JwtParser setCompressionCodecResolver(CompressionCodecResolver var1);
boolean isSigned(String var1);
Jwt parse(String var1) throws ExpiredJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
<T> T parse(String var1, JwtHandler<T> var2) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
Jwt<Header, String> parsePlaintextJwt(String var1) throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
Jwt<Header, Claims> parseClaimsJwt(String var1) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
Jws<String> parsePlaintextJws(String var1) throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
Jws<Claims> parseClaimsJws(String var1) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
}
Claims認證很難解釋清楚,甚至我都找不到一個合適的中文詞語來翻譯它。只好用一個比喻來說明。
- 在實行社保卡之前,我們去醫院看病的時候,需要拿着身份證去辦理一張就診卡,辦卡的工作人員校驗完你的身份證以後,會將你的個人信息錄入到卡里面。當你去找醫生就診的時候,醫生掃描一下你的就診卡,就知道了你的所有信息。這個就診卡就相當於Claims認證中的token,裏面的每條信息就是一個Claim.
package io.jsonwebtoken;
import java.util.Date;
import java.util.Map;
public interface Claims extends Map<String, Object>, ClaimsMutator<Claims> {
String ISSUER = "iss";
String SUBJECT = "sub";
String AUDIENCE = "aud";
String EXPIRATION = "exp";
String NOT_BEFORE = "nbf";
String ISSUED_AT = "iat";
String ID = "jti";
String getIssuer();
Claims setIssuer(String var1);
String getSubject();
Claims setSubject(String var1);
String getAudience();
Claims setAudience(String var1);
Date getExpiration();
Claims setExpiration(Date var1);
Date getNotBefore();
Claims setNotBefore(Date var1);
Date getIssuedAt();
Claims setIssuedAt(Date var1);
String getId();
Claims setId(String var1);
<T> T get(String var1, Class<T> var2);
}
上述Claims接口中定義了一些變量,我找到了相關圖片給你們參考
iss (issuer):簽發人
exp (expiration time):過期時間
sub (subject):主題
aud (audience):受衆
nbf (Not Before):生效時間
iat (Issued At):簽發時間
jti (JWT ID):編號
三、JWT 的特點小結
(1)JWT 默認是不加密,但也是可以加密的
。生成原始 Token 以後,可以用密鑰再加密一次。
(2)JWT 不加密的情況下,不能將祕密數據寫入 JWT。
(3)JWT 不僅可以用於認證,也可以用於交換信息
。有效使用 JWT,可以降低服務器查詢數據庫的次數。
(4)JWT 的最大缺點是,由於服務器不保存 session 狀態,因此無法在使用過程中廢止某個 token,或者更改 token 的權限。也就是說,一旦 JWT 簽發了,在到期之前就會始終有效,除非服務器部署額外的邏輯
。
(5)JWT 本身包含了認證信息,一旦泄露,任何人都可以獲得該令牌的所有權限。
爲了減少盜用,JWT 的有效期應該設置得比較短。對於一些比較重要的權限,使用時應該再次對用戶進行認證。
(6)爲了減少盜用,JWT 不應該使用 HTTP 協議明碼傳輸,要使用 HTTPS 協議傳輸。
The best investment is to invest in yourself
2020.06.04 記錄辰兮的第76篇博客