![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SN1QGMwMGO3ITYzMWOklTZhhTZ4IjMihjY4kTYzcTZ38CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
這篇部落客要是簡單介紹了一下什麼是JWT,以及如何在Spring Boot項目中使用JWT(JSON Web Token)。
1.關于JWT
1.1 什麼是JWT
老生常談的開頭,我們要用這樣一種工具,首先得知道以下幾個問題。 - 這個工具是什麼,這個工具解決了什麼問題 - 是否适用于目前我們所處得業務場景 - 用了之後是否會帶來任何其他問題 - 怎麼用才是最佳實踐
那什麼是JWT呢?以下是我對jwt官網上對JWT介紹的翻譯。
JSON Web Token (JWT)是一種定義了一種緊湊并且獨立的,用于在各方之間使用JSON對象安全的傳輸資訊的一個開放标準(RFC 7519)。
現在我們知道,JWT其實是一種開放标準,用于在多點之間安全地傳輸用JSON表示的資料。在傳輸的過程中,JWT以字元串的形式出現在我們的視野中。該字元串中的資訊可以通過數字簽名進行驗證和信任。
1.2 應用場景
JWT在實際的開發中,有哪些應用場景呢?
1.2.1 授權
這應該算是JWT最常見的使用場景。在前端界面中,一旦使用者登入成功,會接收到後端傳回的JWT。後續的請求都會包含後端傳回的JWT,作為對後端路由、服務以及資源的通路的憑證。
1.2.2 資訊交換
利用JWT在多方之間互相傳遞資訊具有一定的安全性,例如JWT可以用HMAC、RSA非對稱加密算法以及ECDSA數字簽名算法對JWT進行簽名,可以確定消息的發送者是真的發送者,而且使用header和payload進行的簽名計算,我們還可以驗證發送的消息是否被篡改了。
2.JWT的結構
通俗來講JWT由
header.payload.signature
三部分組成的字元串,網上有太多文章介紹這一塊了,是以在這裡就簡單介紹一下就好了。
2.1 header
header
由使用的簽名算法和令牌的類型的組成,例如令牌的類型就是JWT這種開放标準,而使用的簽名算法就是
HS256
,也就是
HmacSHA256
算法。其他的加密算法還有
HmacSHA512
、
SHA512withECDSA
等等。
然後将這個包含兩個屬性的JSON對象轉化為字元串然後使用Base64編碼,最終形成了JWT的header。
2.2 payload
payload
說直白一些就類似你的requestBody中的資料。隻不過是分了三種類型,預先申明好的、自定義的以及私有的。像
iss
發件人,
exp
過期時間都是預先注冊好的申明。
預先申明在載荷中的資料不是強制性的使用,但是官方建議使用。然後這串類似于requestBody的JSON經過Base64編碼形成了JWT的第二部分。
2.3 signature
如果要生成
signature
,就需要使用jwt自定義配置項中的secret,也就是Hmac算法加密所需要的密鑰。将之前經過Base64編碼的header和payload用
.
相連,再使用自定義的密鑰,對該消息進行簽名,最終生成了簽名。
生成的簽名用于驗證消息在傳輸的過程中沒有被更改。在使用非對稱加密算法進行簽名的時候,還可以用于驗證JWT的發件人是否與payload中申明的發件人是同一個人。
3.JWT在Spring項目中的應用場景
3.1 生成JWT
代碼如下。
public String createJwt(String userId, String projectId) throws IllegalArgumentException, UnsupportedEncodingException {
Algorithm al = Algorithm.HMAC256(secret);
Instant instant = LocalDateTime.now().plusHours(outHours).atZone(ZoneId.systemDefault()).toInstant();
Date expire = Date.from(instant);
String token = JWT.create()
.withIssuer(issuer)
.withSubject("userInfo")
.withClaim("user_id", userId)
.withClaim("project_id", projectId)
.withExpiresAt(expire)
.sign(al);
return token;
}
傳入的兩個Claim是項目裡自定義的payload,
al
是選擇的算法,而
secret
就是對資訊簽名的密鑰,
subject
則是該token的主題,
withExpiresAt
辨別了該token的過期時間。
3.2 傳回JWT
在使用者登入系統成功之後,将token作為傳回參數,傳回給前端。
3.3 驗證token
在token傳回給前端之後,後端要做的就是驗證這個token是否是合法的,是否可以通路伺服器的資源。主要可以通過以下幾種方式去驗證。
3.3.1 解析token
使用
JWTVerifier
解析token,這是驗證token是否合法的第一步,例如前端傳過來的token是一串沒有任何意義的字元串,在這一步就可以抛出錯誤。示例代碼如下。
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException e) {
e.printStackTrace();
}
JWTVerifier可以使用用制定secret簽名的算法,指定的claim來驗證token的合法性。
3.3.2 判斷token時效性
判斷了token是有效的之後,再對token的時效性進行驗證。
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
if (jwt.getExpiresAt().before(new Date())) {
System.out.println("token已過期");
return null;
}
} catch (JWTVerificationException e) {
e.printStackTrace();
return null;
}
如果該token過期了,則不允許通路伺服器資源。具體的流程如下。
3.3.3 重新整理過期時間
上面建立token的有效時間是可以配置的,假設是2個小時,并且使用者登入進來連續工作了1小時59分鐘,在進行一個很重要的操作的時候,點選确定,這個時候token過期了。如果程式沒有保護政策,那麼使用者接近兩個小時的工作就成為了無用功。
遇到這樣的問題,我們之前的流程設計必然面對一次重構。可能大家會有疑問,不就是在使用者登入之後,每次操作對去重新整理一次token的過期時間嗎?
那麼問題來了,我們知道token是由
header.payload.signature
三段内容組成的,而過期時間則是屬于payload,如果改變了過期的時間,那麼最終生成的payload的hash則勢必與上一次生成的不同。
換句話說,這是一個全新的token。前端要怎麼接收這個全新的token呢?可想到的解決方案無非就是每次請求,根據response header中的傳回不斷的重新整理的token。但是這樣的方式侵入了前端開發的業務層。使其每一個接口都需要去重新整理token。
大家可能會說,無非就是加一個攔截器嘛,對業務侵入不大啊。即使這部分邏輯是寫在攔截器裡的,但是前端因為token鑒權的邏輯而多出了這部分代碼。而這部分代碼從職能分工上來說,其實是後端的邏輯。
說的直白一些,重新整理token,對token的時效性進行管理,應該是由後端來做。前端不需要也不應該去關心這一部分的邏輯。
3.3.4 redis大法好
綜上所述,重新整理token的過期時間勢必要放到後端,并且不能通過判斷JWT中payload中的expire來判斷token是否有效。
是以,在使用者登入成功之後并将token傳回給前端的同時,需要以某一個唯一表示為key,目前的token為value,寫入Redis緩存中。并且在每次使用者請求成功後,重新整理token的過期時間,流程如下所示。
redis重新整理流程
經過這樣的重構之後,流程就變成了這樣。
在流程中多了一個重新整理token的流程。隻要使用者登入了系統,每一次的操作都會重新整理token的過期時間,就不會出現之前說的在進行某個操作時突然失效所造成資料丢失的情況。
在使用者登入之後的兩個小時内,如果使用者沒有進行任何操作,那麼2小時後再次請求接口就會直接被伺服器拒絕通路。
4.總結
總的來說,JWT中是不建議放特别敏感資訊的。如果沒有用非對稱加密算法的話,把token複制之後直接可以去jwt官網線上解析。如果請求被攔截到了,裡面的所有資訊等于是透明的。
但是JWT可以用來當作一段時間内運作通路伺服器資源的憑證。例如JWT的payload中帶有userId這個字段,那麼就可以對該token辨別的使用者的合法性進行驗證。例如,該使用者目前狀态是否被鎖定?該userId所辨別的使用者是否存在于我們的系統?等等。
并且通過實作token的公用,可以實作使用者的多端同時登入。像之前的登入之後建立token,就限定了使用者隻能同時在一台裝置上登入。
歡迎大家浏覽之前的文章:個人部落格,如果有說的不對的地方,還請不吝賜教。
5.參考
JWT官網