目錄
下一期:spring-security + JWT 整合
1.JWT簡介
JSON Web Token(JWT)是一個開放標準(RFC 7519),它定義了一種緊湊且自包含的方式,用於在各方之間作爲JSON對象安全地傳輸信息。由於此信息是經過數字簽名的,因此可以被驗證和信任。可以使用祕密(使用HMAC算法)或使用RSA或ECDSA的公用/專用密鑰對對JWT進行簽名。
有兩個主要應用場景:
- 授權:這是使用JWT的最常見方案。一旦用戶登錄,每個後續請求將包括JWT(加密過的用戶信息),從而允許用戶訪問該令牌允許的路由,服務和資源。單一登錄是當今廣泛使用JWT的一項功能,因爲它的開銷很小並且可以在不同的域中輕鬆使用。
- 信息交換:JWT是在各方之間安全地傳輸信息的好方法。因爲可以對JWT進行簽名(例如,使用公鑰/私鑰對),所以您可以確定發件人是他們所說的人。另外,由於簽名是使用標頭和有效負載計算的,因此您還可以驗證內容是否未被篡改
承接上一期的內容,這裏我們只探討它在認證授權方面的應用
2.token的結構
JSON Web Token由下面三個部分組成,每個部分使用 . 分割開來
- Header
- Payload
- Signature
整體結果類似於
hhhhhh.ppppppp.sssssss
2.1.Header
標頭通常由兩部分組成:令牌的類型(即JWT)和所使用的Signature(簽名)算法,例如HMAC SHA256或RSA。如下:
{
"alg": "HS256",
"typ": "JWT"
}
然後,此JSON被Base64Url編碼爲JWT的第一部分(Header)。
2.2.Payload
這是令牌主題信息的承載部分,包含三個信息聲明(Claims:Claims are statements about an entity (typically, the user) and additional data)
- 登記信息聲明(Registered claims):這部分包含一些聲明信息,不是強制性的,但是推薦,如:(注意:JWT規定聲明的信息名長度爲三個字符,因爲JWT信息較爲緊湊)
- iss(發佈者)
- exp(到期時間)
- sub(主體)
- aud(接受者)
- 公開聲明信息(Public claims):這些可以由使用JWT的人員隨意定義。但是爲避免衝突,應在IANA JSON Web令牌註冊表中定義它們,或將其定義爲包含抗衝突名稱空間的URI
- 私有聲明信息(Private claims):這些是自定義聲明,旨在在同意使用它們的各方之間共享信息,既不是註冊聲明也不是公共聲明。
類似於如下結構:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然後,此JSON被Base64Url編碼爲JWT的第二部分
2.3.Signature(簽名)
使用Header中定義的編碼方式,和前面定義的信息,我們可以以如下方式創建簽名:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
簽名用於驗證消息在整個過程中沒有更改,並且對於使用私鑰進行簽名的令牌,它還可以驗證JWT的發送者是它所說的真實身份。
2.4.將上面部分合體
由上面的分析可知,JWT的輸出是三個由點分隔的Base64-URL字符串,可以在HTML和HTTP環境中輕鬆傳遞
3.Token工作方式
在身份驗證中,當用戶使用其憑據成功登錄時,將返回JSON Web令牌。由於令牌是憑據,因此必須格外小心以防止安全問題。通常,令牌的保留時間不應超過要求的時間。
由於缺乏安全性,您也不應該將敏感的會話數據存儲在瀏覽器中。
每當用戶想要訪問受保護的路由或資源時,用戶代理通常應在Bearer模式中使用授權頭髮送JWT 。標頭的內容應如下所示:
Authorization: Bearer <token>
在某些情況下,這可以是無狀態授權機制。服務器的受保護路由將在Authorization
標頭中檢查有效的JWT ,如果存在,則將允許用戶訪問受保護的資源。如果JWT包含必要的數據(例如用戶的個人信息),則可以減少查詢數據庫中某些操作的需求,儘管這種情況並非總是如此。
如果令牌是在Authorization
標頭中發送的,則跨域資源共享(CORS)不會成爲問題,因爲它不使用cookie(Session也基於Cokkie)。
4.JWT的Java實現
JWT是一種標準,而它的實現有很多種,幾乎涵蓋大部分編程語言,這裏我們使用Java的一種實現:jjwt
github項目地址:鏈接
這裏先明白幾個名詞:
- JSON Web Signature (JWS) (簽名 A signed JWT is called a 'JWS')
- JSON Web Encryption (JWE) (加密)
- JSON Web Key (JWK) (密鑰)
我們要使用的是一個被簽名的JWT ,即JWS,我們先來創建一個JWS:
4.1.創建JWS
創建一個SecretKey (密鑰)
SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
jjwt提供的密鑰加密方式有很多種,自行選擇,常用HS256
定義也就是設置上面我們分析的那些值:
public static void main(String[] args) {
//密鑰
SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
String jws = Jwts
.builder()
//主題信息,即前面分析的Header和Payload(Claims)
//設置一個頭部信息
.setHeaderParam("kid", "myKeyId")
//也可設置多個頭部信息
// .setHeader(new HashMap<>(3))
//或多個Claims
// .setClaims(new HashMap<String, Object>(3))
//標準的Claims
//令牌發出者
.setIssuer("me")
// 發出時間
.setIssuedAt(new Date())
//主體信息
.setSubject("Bob")
//令牌接受者
.setAudience("you")
//失效日期
.setExpiration(new Date(System.currentTimeMillis() + 3000))
//a java.util.Date
.setNotBefore(new Date(System.currentTimeMillis() + 2000))
//just an example id
.setId(String.valueOf(UUID.randomUUID()))
//自定義Claims
.claim("hello", "world")
//指定密鑰
.signWith(key)
//調用compact()方法進行壓縮和簽名,生成最終的jws
.compact();
System.out.println(jws);
}
結果:eyJraWQiOiJteUtleUlkIiwiYWxnIjoiSFMyNTYifQ.eyJpc3MiOiJtZSIsImlhdCI6MTU5MTg2Mjc1OCwic3ViIjoiQm9iIiwiYXVkIjoieW91IiwiZXhwIjoxNTkxODYyNzYxLCJuYmYiOjE1OTE4NjI3NjAsImp0aSI6IjgyMDJjMWQ4LWFlMzQtNGEwZS1hZjFkLWM0OTY0MTZhZjhlZiIsImhlbGxvIjoid29ybGQifQ.y4FMmiuxQ5huH2h_gYkT5awUiay1LO5kxt4GRNJlEnc
注意:.setClaims() 一定要在標準的Claims之前使用,否則會覆蓋標準的Claims
4.2.讀取JWS
直接給出代碼:
public static void main(String[] args) {
SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
String token = JwtService.getToken(key);
System.out.println(token);
Jws<Claims> jws;
jws = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token);
System.out.println(jws.getHeader());
System.out.println(jws.getBody());
System.out.println(jws.getSignature());
}
result:
eyJraWQiOiJteUtleUlkIiwiYWxnIjoiSFMyNTYifQ.eyJpc3MiOiJtZSIsImlhdCI6MTU5MTg2MzY1MSwic3ViIjoiQm9iIiwiYXVkIjoieW91IiwiZXhwIjoxNTkxODYzNjU0LCJqdGkiOiIyNGZiNDFkMy1jY2UyLTQzZDMtYjMyOC0xYzBlNmYzYjg1Y2YiLCJoZWxsbyI6IndvcmxkIn0.OI2YnQlVm-VNTgk_SRt2V6yhWoc0RqIIl11XcBaSopo
{kid=myKeyId, alg=HS256}
{iss=me, iat=1591863651, sub=Bob, aud=you, exp=1591863654, jti=24fb41d3-cce2-43d3-b328-1c0e6f3b85cf, hello=world}
OI2YnQlVm-VNTgk_SRt2V6yhWoc0RqIIl11XcBaSopo
可見,Header 和 Payload 都是以 Map格式存儲的
還要注意一點,這裏的加密和解密的 SecretKey 必須是同一個, 即使是相同算法的的兩個也不行
下一期我們就來使用 jjwt 完成Spring Security 的記住我的功能