JWT 思維導圖,讓 JWT 不再難懂
一般情況下,web專案都是通過session進行認證,每次請求資料時,都會把jsessionid放在cookie中,以便與服務端保持會話。
前後端分離專案中,通過token進行認證(登入時,生成唯一的token憑證),每次請求資料時,都會把token放在header中,服務端解析token,並確定使用者身份及使用者許可權,資料通過json互動。
但是token一般都是UUID生成的一個隨機碼,作為一個key使用,從快取中獲取具體的使用者資訊。所以一般需要一個儲存介質來儲存token和使用者資訊。在一些場景中,如單點登入時候有點麻煩。
有沒一種更方便的方式呢?答案是有的,就是我們今天要講的jwt。jwt也算是一個特殊的token,不過jwt中自帶了使用者的相關資訊,所以不需要儲存介質,只需要驗證簽名保證安全的前提下就可以直接獲取到使用者的相關資訊。
在講jwt之前,我們先回顧一下session、token的相關內容。
session與cookie
我們都知道http是無狀態的,所以需要某種機制來識別使用者和儲存使用者的狀態。而這個機制就是session。session是儲存在服務端的,伺服器通過session辨別使用者,然後做許可權認證等。
那如何才知道使用者的session是哪個?這時候cookie就出場了,瀏覽器第一次與伺服器建立連線的時候,伺服器會生成一個sessionid返回瀏覽器,瀏覽器把這個sessionid儲存到cookie當中,以後每次發起請求都會在請求頭cookie中帶上這個sessionid資訊,所以伺服器就是根據這個sessionid所以key獲取到具體session。
google瀏覽器中檢視cookie內容的方法有兩個:
(一)F12,檢視具體請求連結的請求頭資訊
(二)點選瀏覽器輸入框的認證小鎖,可以檢視這個域名的相關cookie資訊。
涉及到叢集環境得話,session需要弄成分散式session,從而保證多個應用的會話狀態一致性。spring專案可以使用spring session+redis來解決session共享問題。shiro專案可以重寫redis版SessionDAO,把會話資訊存到redis中實現共享。
接下來我們再來聊聊token。
Token
token,就是我們常說的使用者身份令牌。只有涉及到受限資源的訪問時候才需要身份令牌,所以,在訪問開放資源時候http中是沒有token的資訊的,也即是說這時候會話是完全無狀態的。token的是在使用者登入以後生成的。使用者登入之後我們會生成一個token作為key儲存使用者的資訊並返回給客戶端。儲存方式set(token,使用者資訊)儲存到redis等介質。
之後客戶端發起的請求只要在請求頭中附帶token的資訊就可以完成身份認證。
開源專案renren-fast採用了前後分離的機制,使用token來完成身份認證,並且集成了shiro框架,所以想實戰的可以去clone下來玩玩~
-
https://gitee.com/renrenio/renren-fast
(只能幫你到這了~)
好了,說了這麼拓展知識,接下來我們進入我們的正題!jwt。
jwt是什麼
Json web token (JWT), 是為了在網路應用環境間傳遞宣告而執行的一種基於JSON的開放標準((RFC 7519)。該token被設計為緊湊且安全的,特別適用於分散式站點的單點登入(SSO)場景。
JWT的宣告一般被用來在身份提供者和服務提供者間傳遞被認證的使用者身份資訊,以便於從資源伺服器獲取資源,也可以增加一些額外的其它業務邏輯所必須的宣告資訊,該token也可直接被用於認證,也可被加密。
jwt的特點
-
簡潔(Compact): 可以通過URL,POST引數或者在HTTP header傳送,因為資料量小,傳輸速度也很快
-
自包含(Self-contained):負載中包含了所有使用者所需要的資訊,避免了多次查詢資料庫或快取。
說了這麼多~
真面目:
傻眼了吧?看不懂吧,哈哈哈哈哈~~~~~~~~~
那麼我們一一把這串資料分析一下。
首先從jwt的訊息結構開始分析:
jwt訊息結構
jwt有3個組成部分,分別是
-
頭部(header)
-
載荷(payload)
-
簽證(signature)
先回頭看看jwt真面目那個例子。仔細點,認真點,有沒在茫茫字母和數字中發現兩個.(點號)。
就是這兩個點號把jwt分成了3部分,分別對應著上面說的頭部,載荷,簽證。
先來講講頭部:
Jwt的頭部承載兩部分資訊:
-
宣告型別,這裡是jwt
-
宣告加密的演算法,通常直接使用HMACSHA256,就是HS256了
然後將頭部進行base64編碼構成了第一部分:
(base64懂吧?)
Base64是一種用64個字元來表示任意二進位制資料的方法
Base64是一種任意二進位制到文字字串的編碼方法,常用於在URL、Cookie、網頁中傳輸少量二進位制資料。
Java中可以使用java.util.Base64進行編碼解碼。
(我沒騙你吧?頭部就是這樣來的)
然後我們看第二部分:載荷。
我依稀記得物理老師說過:直接施加在結構上的各種力,習慣上稱為載荷(荷載)。
好了不吹牛了,這裡是承載的意思。也就是說這裡是承載訊息具體內容的地方。
內容又可以分為3中標準
-
標準中註冊的宣告
-
公共的宣告
-
私有的宣告
payload-標準中註冊的宣告 (建議但不強制使用) :
-
iss: jwt簽發者
-
sub: jwt所面向的使用者
-
aud: 接收jwt的一方
-
exp: jwt的過期時間,這個過期時間必須要大於簽發時間
-
nbf: 定義在什麼時間之前,該jwt都是不可用的.
-
iat: jwt的簽發時間
-
jti: jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。
payload-公共的宣告 :
公共的宣告可以新增任何的資訊。一般這裡我們會存放一下使用者的基本資訊(非敏感資訊)。
payload-私有的宣告 :
私有宣告是提供者和消費者所共同定義的宣告。
需要注意的是,不要存放敏感資訊,不要存放敏感資訊,不要存放敏感資訊!!!
因為:這裡也是base64編碼,任何人獲取到jwt之後都可以解碼!!(產品應該就不懂)
好了,請容許我Base64解碼一下載荷部分內容到底是啥。
sub和iat是標準宣告,分別代表所面向的使用者和jwt簽發時間。
從上面我知道了:
-
這個是發給一個賬號是1234567890的使用者(也許是ID)
-
名字叫John Doe
-
簽發時間是1516239022(2018/1/18 9:30:22)
(牛逼~)
只剩下最後一部分了,待我一個閃現,外加一個大招秒殺它!
(emmmmm~~~,請求集合!!求救~)
簽證部分貌似和Base64沒啥關係呀,那到底是啥的~
別急,先來說說簽證用來幹啥的,其實就是一個簽名信息,使用了自定義的一個金鑰然後加密後的結果,目的就是為了保證簽名的資訊沒有被別人改過!(也就是保證jwt安全可用)
頭部那裡我們不是定義了一個加密演算法麼,就是它
也就是說,簽證部分的資訊有3個組成部分:
-
頭部-header (base64後的)
-
載荷-payload (base64後的)
-
金鑰-secret
然後HMACSHA256只有兩個引數,
-
base64後的頭部 + "." + base64後的載荷
-
金鑰-secret
好了,這部分我就不做程式碼演示了。因為有現成的工具類可以直接用,哈哈
下面我們就介紹一下常用的生成jwt的工具類:
-
https://jwt.io/
以上就是官網給我們介紹的幾種java可用的jar包。其中,io.jsonwebtoken是最常用的工具包。
使用步驟如下:
第一步,匯入jar包:
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
第二步:稍微封裝一下,方便整合到專案中:
/**
* jwt工具類
* @author chenshun
* @email [email protected]
* @date 2017/9/21 22:21
*/
@ConfigurationProperties(prefix = "renren.jwt")
@Component
public class JwtUtils {
private Logger logger = LoggerFactory.getLogger(getClass());
private String secret;
private long expire;
private String header;
/**
* 生成jwt token
*/
public String generateToken(long userId) {
Date nowDate = new Date(); //過期時間
Date expireDate = new Date(nowDate.getTime() + expire * 1000);
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(userId+"")
.setIssuedAt(nowDate)
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Claims getClaimByToken(String token) {
try {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
logger.debug("validate is token error ", e);
return null;
}
} /**
* token是否過期
* @return true:過期
*/
public boolean isTokenExpired(Date expiration) {
return expiration.before(new Date());
}
//getter、setter
}
生成jwt:
獲取jwt的有效資訊:
嘿嘿、以上例子來自之前說個的renren-fast專案。所以呀,你還是去看看吧~
(又學了一個技能,好有成就感!)
使用場景
閒聊一下jwt的使用場景
詳細可以看看這篇文章:
-
https://baijiahao.baidu.com/s?id=1598976581711450442&wfr=spider&for=pc
總結
最後的最後,再來個小總結:
1、在Web應用中,別再把JWT當做session使用,絕大多數情況下,傳統的cookie-session機制工作得更好
2、JWT適合一次性的命令認證,頒發一個有效期極短的JWT,即使暴露了危險也很小,由於每次操作都會生成新的JWT,因此也沒必要儲存JWT,真正實現無狀態。