JWT結構化令牌(what、why、how)

理解JWT結構化令牌,能讓自己對認證、授權流程有更深入的理解。

JWT結構化令牌

關於什麼是 JWT,官方定義是這樣描述的:

JSON Web Token(JWT)是一個開放標準(RFC 7519),它定義了一種緊湊的、自包含的方式,用於作爲 JSON 對象在各方之間安全地傳輸信息。

這個定義是不是很費解?我們簡單理解下,JWT 就是用一種結構化封裝的方式來生成 token 的技術。結構化後的 token 可以被賦予非常豐富的含義,這也是它與原先毫無意義的、隨機的字符串形式 token 的最大區別。

結構化之後,令牌本身就可以被“塞進”一些有用的信息.

JWT 這種結構化體可以分爲 HEADER(頭部)、PAYLOAD(數據體)和 SIGNATURE(簽名)三部分。經過簽名之後的 JWT 的整體結構,是被句點符號分割的三段內容,結構爲 header.payload.signature 。比如下面這個示例:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

注意:JWT 內部沒有換行,這裏只是爲了展示方便,纔將其用三行來表示。

你可能會說,這個 JWT 令牌看起來也是毫無意義的、隨機的字符串啊。確實,你直接去看這個字符串是沒啥意義,但如果你把它拷貝到https://jwt.io/ 網站的在線校驗工具中,就可以看到解碼之後的數據:

  • HEADER 表示裝載令牌類型和算法等信息,是 JWT 的頭部。其中,typ 表示第二部分 PAYLOAD 是 JWT 類型,alg 表示使用 HS256 對稱簽名的算法。

  • PAYLOAD 表示是 JWT 的數據體,代表了一組數據。其中,sub(令牌的主體,一般設爲資源擁有者的唯一標識)、exp(令牌的過期時間戳)、iat(令牌頒發的時間戳)是 JWT 規範性的聲明,代表的是常規性操作。更多的通用聲明,你可以參考RFC 7519 開放標準。不過,在一個 JWT 內可以包含一切合法的 JSON 格式的數據,也就是說,PAYLOAD 表示的一組數據允許我們自定義聲明。

  • SIGNATURE 表示對 JWT 信息的簽名。那麼,它有什麼作用呢?我們可能認爲,有了 HEADER 和 PAYLOAD 兩部分內容後,就可以讓令牌攜帶信息了,似乎就可以在網絡中傳輸了,但是在網絡中傳輸這樣的信息體是不安全的,因爲你在“裸奔”啊。所以,我們還需要對其進行加密簽名處理,而 SIGNATURE 就是對信息的簽名結果,當受保護資源接收到第三方軟件的簽名後需要驗證令牌的簽名是否合法。

0Auth2.0 與 JWT結構化令牌

OAuth2.0 本質是一個授權協議。
OAuth2.0 的核心是授權許可,更進一步說就是令牌機制

授權服務的核心就是頒發訪問令牌,而 OAuth 2.0 規範並沒有約束訪問令牌內容的生成規則,只要符合唯一性、不連續性、不可猜性就夠了。這就意味着,我們可以靈活選擇令牌的形式,既可以是沒有內部結構且不包含任何信息含義的隨機字符串,也可以是具有內部結構且包含有信息含義的字符串。

JWT令牌就是使用最多的結構化令牌。

JWT令牌的使用

JWT令牌傳輸過程中需要進行 Base64 編碼以防止亂碼,同時還需要進行簽名及加密處理來防止數據信息泄露
如果是我們自己處理這些編碼、加密等工作的話,就會增加額外的編碼負擔。好在,我們可以藉助一些開源的工具來幫助我們處理這些工作。比如,我在下面的 Demo 中,給出了開源 JJWT(Java JWT)的使用方法。

JJWT 是目前 Java 開源的、比較方便的 JWT 工具,封裝了 Base64URL 編碼和對稱 HMAC、非對稱 RSA 的一系列簽名算法。使用 JJWT,我們只關注上層的業務邏輯實現,而無需關注編解碼和簽名算法的具體實現,這類開源工具可以做到“開箱即用”。

<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
String sharedTokenSecret="HelloWorld";//密鑰
Key key = new SecretKeySpec(sharedTokenSecret.getBytes(),
                SignatureAlgorithm.HS256.getJcaName());

//生成JWT令牌
String jwts=
Jwts.builder().setHeaderParams(headerMap).setClaims(payloadMap).signWith(key,SignatureAlgorithm.HS256).compact()

//解析JWT令牌
Jws<Claims> claimsJws =Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(jwts);
JwsHeader header = claimsJws.getHeader();
Claims body = claimsJws.getBody();  

JWT令牌缺陷與解決方案

JWT 格式令牌的最大問題在於 “覆水難收”,也就是說,沒辦法在使用過程中修改令牌狀態。用戶或者第三方使用過程中,無法主動暫停授權。
爲了解決這個問題,我們可以把 JWT 令牌存儲到遠程的分佈式內存數據庫中嗎?顯然不能,因爲這會違背 JWT 的初衷(將信息通過結構化的方式存入令牌本身)。因此,我們通常會有兩種做法:

  • 一是,將每次生成 JWT 令牌時的祕鑰粒度縮小到用戶級別,也就是一個用戶一個祕鑰。這樣,當用戶取消授權或者修改密碼後,就可以讓這個密鑰一起修改。一般情況下,這種方案需要配套一個單獨的密鑰管理服務。
  • 二是,在不提供用戶主動取消授權的環境裏面,如果只考慮到修改密碼的情況,那麼我們就可以把用戶密碼作爲 JWT 的密鑰。當然,這也是用戶粒度級別的。這樣一來,用戶修改密碼也就相當於修改了密鑰。

爲什麼要使用JWT結構化令牌

  1. JWT 的核心思想,就是用計算代替存儲,有些 “時間換空間” 的 “味道”。當然,這種經過計算並結構化封裝的方式,也減少了“共享數據庫” 因遠程調用而帶來的網絡傳輸消耗,所以也有可能是節省時間的。
  2. 也是一個重要特性,是加密。因爲 JWT 令牌內部已經包含了重要的信息,所以在整個傳輸過程中都必須被要求是密文傳輸的,這樣被強制要求了加密也就保障了傳輸過程中的安全性。這裏的加密算法,既可以是對稱加密,也可以是非對稱加密。
  3. 使用 JWT 格式的令牌,有助於增強系統的可用性和可伸縮性。這一點要怎麼理解呢?我們前面講到了,這種 JWT 格式的令牌,通過“自編碼”的方式包含了身份驗證需要的信息,不再需要服務端進行額外的存儲,所以每次的請求都是無狀態會話。這就符合了我們儘可能遵循無狀態架構設計的原則,也就是增強了系統的可用性和伸縮性。

JWT令牌的生命週期。

  1. 令牌的自然過期過程,這也是最常見的情況。這個過程是,從授權服務創建一個令牌開始,到第三方軟件使用令牌,再到受保護資源服務驗證令牌,最後再到令牌失效。同時,這個過程也不排除主動銷燬令牌的事情發生,比如令牌被泄露,授權服務可以做主讓令牌失效。
  2. 訪問令牌失效之後可以使用刷新令牌請求新的訪問令牌來代替失效的訪問令牌,以提升用戶使用第三方軟件的體驗。
  3. 主動發起令牌失效的請求,然後授權服務收到請求之後讓令牌立即失效。

覆盤、成長,加油!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章