深入淺出 JSON Web Token

心智極客發表於2019-12-01

原理

Web 應用可分為客戶端和服務端,這兩者之間經常需要進行身份認證。

深入淺出 Json Web Token

由於 HTTP 是無狀態協議,不能儲存認證後的使用者狀態,因此,每一次傳送請求都需要重複的進行認證。

深入淺出 Json Web Token

為了解決該問題,通常會使用 Cookie 來管理 Session,來實現使用者狀態管理。

深入淺出 Json Web Token

伺服器需要記錄 Session 資訊,當存在多臺伺服器時,這些伺服器就需要共享 Session。例如,將 Session 資料進行持久化儲存,每臺伺服器都從持久層中獲取 Session。這種方案也有風險,當持久層突然失效時,所有登入資訊都將失效。

深入淺出 Json Web Token

由此可見,服務端儲存認證狀態是一件棘手的事,既然如此,何不將資料全都儲存在客戶端呢?Json Web Token 就是基於該思路提出的一種認證方案。

我們從 JSON Web Token 的命名就可以得出一些資訊

  • JSON - 資料是 JSON 格式的
  • Web - 是用於 Web 的
  • Token - Token 是令牌的意思,代表了 JWT 是一種認證的憑證

深入淺出 Json Web Token

JWT 的認證過程如下:

  1. 客戶端傳送登入資訊(使用者 ID,密碼)
  2. 服務端基於金鑰生成 JWT,返回給客戶端
  3. 客戶端在接下來的請求中將 Token 放在頭部中一起傳送給服務端
  4. 服務端對 JWT 進行驗證

深入淺出 Json Web Token

生成的 JWT 的格式如下

深入淺出 Json Web Token

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);

利用得到的 HeaderPayload ,以及服務端儲存的金鑰來計算出簽名

$encryp = $base64Header.".".$base64Payload;
$signature = hash_hmac('sha256', $encryp, $secret, true);
$computedBase64Signature = base64url($signature);

將計算出來的簽名與客戶端傳送的簽名進行對比

if($computedBase64Signature === $base64Signature){
    echo "認證成功!";
}

參考連結:

點選 連結,免費加入心智極客的知識星球分享群,共同成長。

相關文章