11-26JWT 基礎教程
一、前言
針對前後端分離的專案,大多是通過 token 進行身份認證來進行互動,今天將介紹一種簡單的建立 token 的方式 -- JWT。
二、基本介紹
# 2.1 定義
JSON Web Token(JWT)是一個非常輕巧的規範。這個規範允許我們使用 JWT 在使用者和伺服器之間傳遞安全可靠的資訊。
# 2.2 組成部分
一個 JWT 實際上就是一個字串,它由三部分組成,頭部、載荷與簽名。前兩部分需要經過 Base64 編碼,後一部分通過前兩部分 Base64 編碼後再加密而成。
如果讀者不理解上邊的陳述,不要緊,下文會詳細講解。
頭部(Header)
頭部用於描述關於該 JWT 的最基本的資訊,例如其型別以及簽名所用的演算法等,也可以被表示成一個 JSON 物件。例如:
- {"typ":"JWT","alg":"HS256"}
在頭部指明瞭簽名演算法是 HS256 演算法。
經過 Base64 編碼得到: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 (第一部分)。下文參考資料提供線上加密/解密網址,感興趣的讀者可以自己嘗試。
載荷(playload)
載荷就是存放有效資訊的地方。這些有效資訊包含三個部分:
(1)標準中註冊的宣告(建議但不強制使用)
- iss: jwt簽發者
- sub: jwt所面向的使用者
- aud: 接收jwt的一方
- exp: jwt的過期時間,這個過期時間必須要大於簽發時間
- nbf: 定義在什麼時間之前,該jwt都是不可用的.
- iat: jwt的簽發時間
- jti: jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊
(2)公共的宣告
- 公共的宣告可以新增任何的資訊,一般新增使用者的相關資訊或其他業務需要的必要資訊,但不建議新增敏感資訊,因為該部分在客戶端可解密。
例如:
- {"id":"123456","name":"MoonlightL","sex":"male"}
將該 json 字串進行 Base64 編碼得到: eyJpZCI6IjEyMzQ1NiIsIm5hbWUiOiJNb29ubGlnaHRMIiwic2V4IjoibWFsZSJ9 (第二部分)。
(3)私有的宣告
- 私有宣告是提供者和消費者所共同定義的宣告,一般不建議存放敏感資訊,因為base64 是對稱解密的,意味著該部分資訊可以歸類為明文資訊。
注意:載荷中的這3個宣告並不是都要同時設定。
簽證(signature)
jwt的第三部分是一個簽證資訊。
- 這個部分需要 Base64 加密後的 header 和 Base64 加密後的 payload 使用 “.” 連線組成的字串,然後通過 header 中宣告的加密方式進行加鹽 secret 組合加密,然後就構成了 jwt 的第三部分。
即將 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEyMzQ1NiIsIm5hbWUiOiJNb29ubGlnaHRMIiwic2V4IjoibWFsZSJ9 進行 HS256 演算法加密(header 定義的)得到:
e5dda3f17226c1c6ca7435cd17f83ec0c74d62bd8e8386e1a178cd970737f09f(第三部分)。
最後,我們將上述的 3 個部分的字串通過 “.” 進行拼接得到 JWT:
- eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEyMzQ1NiIsIm5hbWUiOiJNb29ubGlnaHRMIiwic2V4IjoibWFsZSJ9.e5dda3f17226c1c6ca7435cd17f83ec0c74d62bd8e8386e1a178cd970737f09f
為了驗證上述生成的 jwt 是否合法,我們可以登入 ofollow,noindex" target="_blank">JWT 官網 ,官網介面提供 JWT 解密功能,將生成好的 JWT 複製到如下圖中進行解密:
在實際開發中,使用者登入成功後,後端生成 jwt 返回給前端,之後,前端與後端互動時攜帶 jwt 讓後端驗證 jwt 的合法性。
三、實戰入門
通過上述的介紹,我們已經瞭解到什麼是 JWT 以及 JWT 生成的規則,現在我們通過程式碼方式來生成 JWT。
JWT 官網提供了通過不同程式語言來建立 JWT 的工具類/庫,此次測試我們選用 JJWT 。
# 3.1 新增依賴
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt-api</artifactId>
- <version>0.10.5</version>
- </dependency>
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt-impl</artifactId>
- <version>0.10.5</version>
- <scope>runtime</scope>
- </dependency>
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt-jackson</artifactId>
- <version>0.10.5</version>
- <scope>runtime</scope>
- </dependency>
# 3.2 編碼
- import java.security.Key;
- import java.util.Date;
- import java.util.UUID;
- import org.junit.Test;
- import io.jsonwebtoken.Claims;
- import io.jsonwebtoken.Jws;
- import io.jsonwebtoken.JwtBuilder;
- import io.jsonwebtoken.JwtException;
- import io.jsonwebtoken.Jwts;
- import io.jsonwebtoken.SignatureAlgorithm;
- import io.jsonwebtoken.security.Keys;
- public class JWTTest {
- @Test
- public void testJWT() {
- Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
- System.out.println("=============建立 JWT===========");
- Date now = new Date();
- JwtBuilder builder= Jwts.builder()
- .setId(UUID.randomUUID().toString()) // 載荷-標準中註冊的宣告
- .setSubject("admin") // 載荷-標準中註冊的宣告
- .setIssuedAt(now) // 載荷-標準中註冊的宣告,表示簽發時間
- .claim("id", "123456") // 載荷-公共的宣告
- .claim("name", "MoonlightL") // 載荷-公共的宣告
- .claim("sex", "male") // 載荷-公共的宣告
- .signWith(key); // 簽證
- String jwt = builder.compact();
- System.out.println("生成的 jwt :" +jwt);
- System.out.println("=============解析 JWT===========");
- try {
- Jws<Claims> result = Jwts.parser().setSigningKey(key).parseClaimsJws(jwt);
- // 以下步驟隨實際情況而定,只要上一行程式碼執行不拋異常就證明 jwt 是有效的、合法的
- Claims body = result.getBody();
- System.out.println("載荷-標準中註冊的宣告 id:" + body.getId());
- System.out.println("載荷-標準中註冊的宣告 subject:" + body.getSubject());
- System.out.println("載荷-標準中註冊的宣告 issueAt:" + body.getIssuedAt());
- System.out.println("載荷-公共的宣告的 id:" + result.getBody().get("id"));
- System.out.println("載荷-公共的宣告的 name:" + result.getBody().get("name"));
- System.out.println("載荷-公共的宣告的 sex:" + result.getBody().get("sex"));
- } catch (JwtException ex) { // jwt 不合法或過期都會拋異常
- ex.printStackTrace();
執行結果:
- =============建立 JWT===========
- 生成的 jwt :eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI3ZjZmZjRlMC04YjM5LTQyYjUtOGRkNS0xN2M4ZjM5ZmZhNzMiLCJzdWIiOiJhZG1pbiIsImlhdCI6MTU0MzIwNTI4OSwiZXhwIjoxNTQzMjA1MzQ5LCJpZCI6IjEyMzQ1NiIsIm5hbWUiOiJNb29ubGlnaHRMIiwic2V4IjoibWFsZSJ9.BtEi-GCj5mCunXD_g0Cra7CSE_bMxhTzlOELWKc17I8
- =============解析 JWT===========
- 載荷-標準中註冊的宣告 id:7f6ff4e0-8b39-42b5-8dd5-17c8f39ffa73
- 載荷-標準中註冊的宣告 subject:admin
- 載荷-標準中註冊的宣告 issueAt:Mon Nov 26 12:08:09 CST 2018
- 載荷-公共的宣告的 id:123456
- 載荷-公共的宣告的 name:MoonlightL
- 載荷-公共的宣告的 sex:male
注意:加密和解密 JWT 必須是同一個 Key 物件
注意:解密 JWT 時,必須要抓取 JwtException 異常,只要抓取到該異常說明該 JWT 不可用了
JJWT 庫還有其他使用方式,具體資料請檢視下邊提供的參考資料。