原理
Web 應用可分為客戶端和服務端,這兩者之間經常需要進行身份認證。
由於 HTTP 是無狀態協議,不能儲存認證後的使用者狀態,因此,每一次傳送請求都需要重複的進行認證。
為了解決該問題,通常會使用 Cookie 來管理 Session,來實現使用者狀態管理。
伺服器需要記錄 Session 資訊,當存在多臺伺服器時,這些伺服器就需要共享 Session。例如,將 Session 資料進行持久化儲存,每臺伺服器都從持久層中獲取 Session。這種方案也有風險,當持久層突然失效時,所有登入資訊都將失效。
由此可見,服務端儲存認證狀態是一件棘手的事,既然如此,何不將資料全都儲存在客戶端呢?Json Web Token 就是基於該思路提出的一種認證方案。
我們從 JSON Web Token 的命名就可以得出一些資訊
- JSON - 資料是 JSON 格式的
- Web - 是用於 Web 的
- Token - Token 是令牌的意思,代表了 JWT 是一種認證的憑證
JWT 的認證過程如下:
- 客戶端傳送登入資訊(使用者 ID,密碼)
- 服務端基於金鑰生成 JWT,返回給客戶端
- 客戶端在接下來的請求中將 Token 放在頭部中一起傳送給服務端
- 服務端對 JWT 進行驗證
生成的 JWT 的格式如下
JWT 可分為三部分,用 .
符號隔開
- Header 頭部
- Payload 負載
- Signature 簽名
Header 用於攜帶一些後設資料,比如加密演算法與型別
{
"alg": "HS256",
"typ": "JWT"
}
Payload 攜帶令牌的具體內容,常用的內容如下
- iss (issuer) 簽發人
- exp (expiration time) 過期時間
- sub (subject) 主題
- aud (audience) 受眾
- nbf (Not Before) 生效時間
- iat (Issued At) 簽發時間
- jti (JWT ID) 編號
可根據需要自行選擇,也可以自己定義
{
"sub": "1",
"name": "Mind Geek",
"admin": true
}
Signature 是加密 Header 和 Payload 後得到的簽名,防止資料篡改,加密公式如下
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
mind-geek-jwt
)
解讀
- HMACSHA256 是 Header 指定的演算法
mind-geek-jwt
則是服務端定義的金鑰- JWT 作為一個令牌(token),有些場合可能會放到 URL 中。Base64 有三個字元
+
、/
和=
,在 URL 裡面有特殊含義,所以要被替換=
被省略、+
替換成-
,/
替換成_
。這就是 Base64URL 演算法。
示例
加密
定義 Header
$header = [
'alg' => 'HS256',
'typ' => 'JWT',
];
定義 Payload
$payload = [
'sub' => 1,
'name' => 'Mind Geek',
'admin' => true,
];
定義金鑰
$secret = 'mind-geek-jwt';
將 Header 和 Payload 陣列轉化成 Json,再使用 Base64URL 演算法進行編碼。
function base64url(string $string)
{
return str_replace('=', '', strtr(base64_encode($string), '+/', '-_'));
}
// Header
$base64Header = base64url(json_encode($header));
// Payload
$base64Payload = base64url(json_encode($payload));
將編碼後的 Header 和 Payload 用 .
拼接起來
$encryp = $base64Header.".".$base64Payload;
對其進行加密,加密後再進行 Base64URL 編碼,得到 Signature
$signature = hash_hmac('sha256', $encryp, $secret, true);
$base64Signature = base64url($signature);
拼接之後就是 JWT 字串了
$token = $base64Header.".".$base64Payload.".".$base64Signature;
驗證
獲取客戶端傳送的 JWT
$token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEsIm5hbWUiOiJNaW5kIEdlZWsiLCJhZG1pbiI6dHJ1ZX0.0_dneYOin4yWRYlD-KmfvGEY6AhjA_zDyyvPhgYq2sU';
解析 JWT,得到編碼過後的 Header、Payload 以及 Signature
list($base64Header, $base64Payload, $base64Signature) = explode('.', $token);
利用得到的 Header
和 Payload
,以及服務端儲存的金鑰來計算出簽名
$encryp = $base64Header.".".$base64Payload;
$signature = hash_hmac('sha256', $encryp, $secret, true);
$computedBase64Signature = base64url($signature);
將計算出來的簽名與客戶端傳送的簽名進行對比
if($computedBase64Signature === $base64Signature){
echo "認證成功!";
}
參考連結:
- JSON Web Token 入門教程 - 阮一峰的網路日誌
- JSON Web Tokens - jwt.io
- 圖解HTTP (豆瓣)
- 理解 cookie、session、token、jwt | PHP 技術論壇
點選 連結,免費加入心智極客的知識星球分享群,共同成長。