JSON Web Token

JSON Web Token（JWT，建議讀音 dʒ，與英語單詞「jot」同音）是一項擬議中的網際網路標準，用於建立帶有可選簽名和／或可選加密的數據，其有效載荷中保存用於斷言若干聲明的 JSON。這些令牌可以使用共享秘密，或使用公鑰／私鑰進行簽名。

例如，伺服器可以生成一個帶有「已作為管理員登錄」這一聲明的令牌，並將其提供給客戶端。客戶端隨後便可使用該令牌來證明自己已以管理員身份登錄。令牌可以由某一方的私鑰（通常是伺服器的私鑰）簽名，以便任何一方隨後都能驗證令牌是否合法。若另一方通過適當且可信的方式持有對應的公鑰，則其也能夠驗證該令牌的合法性。這些會話令牌被設計為緊湊、URL安全、且易於使用，尤其適用於網頁瀏覽器中的單點登錄（SSO）場景。JWT 聲明通常可用於在身份提供者與服務提供者之間傳遞已認證使用者的身份資訊，或按業務流程需要傳遞其他類型的聲明。

JWT 依賴其他基於 JSON 的標準：JSON Web Signature 與 JSON Web Encryption。

## 結構

標頭：用於標識生成簽名時所使用的演算法。在下面的例子中，`HS256` 表示此令牌使用 HMAC-SHA256 進行簽名。

常見的密碼學演算法包括使用 SHA-256 的 HMAC（HS256），以及使用 SHA-256 的 RSA 數字簽名（RS256）。JWA（JSON Web Algorithms）RFC 7518 還為認證與加密引入了更多演算法。

```json
{
  "alg": "HS256",
  "typ": "JWT"
}
```

有效載荷：包含一組聲明。JWT 規範定義了七個已註冊聲明名稱，它們是通常包含在令牌中的標準欄位。此外，也常會根據令牌用途加入自定義聲明。

以下示例包含標準的簽發時間聲明（`iat`）以及一個自定義聲明（`loggedInAs`）。

```json
{
  "loggedInAs": "admin",
  "iat": 1422779638
}
```

簽名：用於安全地驗證令牌。簽名的計算方式是先將標頭與有效載荷分別使用 Base64url 編碼，再以句點將二者連接成字串。隨後，將該字串輸入標頭中指定的密碼學演算法中。本例使用帶有共享秘密的 HMAC-SHA256（也定義了公鑰演算法）。Base64url 編碼與 Base64 類似，但使用不同的非字母數字字符，並省略填充字符。

```javascript
HMAC_SHA256(
  secret,
  base64urlEncoding(header) + '.' +
  base64urlEncoding(payload)
)
```

以上三部分分別使用 Base64url 編碼 編碼，再以句點連接，構成 JWT：

```typescript
const token: string = base64urlEncoding(header) + '.' + base64urlEncoding(payload) + '.' + base64urlEncoding(signature)
```

以上數據配合秘密「secretkey」會生成如下令牌：

```text
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN
_oWnFSRgCzcmJmMjLiuyu5CSpyHI
```

（上述 JSON 字串會被格式化為不帶換行與空格的 UTF-8 字節陣列。這一點很重要，因為哪怕數據僅有細微變化，都會影響最終生成的令牌。）

由此生成的令牌可以方便地嵌入到 HTML 與 HTTP 中。

## 用途

在認證場景中，當使用者成功登錄後，通常會返回一個 JSON Web Token（JWT）。該令牌應通過安全機製發送給客戶端，例如使用HTTP-only Cookie。通常不建議將 JWT 存儲於瀏覽器的本地存儲機制中，例如 localStorage 或 sessionStorage。這是因為運行在客戶端的 JavaScript（包括瀏覽器擴展）可以訪問這些存儲機制，從而暴露 JWT 並危及安全性。若要在需要與跨來源 API 進行認證時使用 HTTP-only Cookie，較佳做法是使用 `credentials` 屬性，告知瀏覽器在 Fetch 調用時自動將 Cookie 發送至外部 API，例如：

```javascript
fetch('https://api.example.com/data', {
  method: 'GET',
  credentials: 'include' // 告知浏览器一并发送 Cookie 等凭证
})
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));
```

使用這種方式時，JWT 不會暴露給客戶端側 JavaScript；這是在遵循安全最佳實踐的同時使用 JWT 的較佳方式。對於無人值守的進程，客戶端也可以直接使用預共享密鑰生成並簽署自己的 JWT，然後將其傳遞給兼容 OAuth 的服務，如下所示：

```mime
POST /oauth2/token
Content-type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=eyJhb...
```

若客戶端傳遞了有效的 JWT 斷言，伺服器將生成一個可用於調用應用程式的 `access_token`，並將其返回給客戶端：

```json
{
  "access_token": "eyJhb...",
  "token_type": "Bearer",
  "expires_in": 3600
}
```

當客戶端希望訪問受保護的路由或資源時，使用者代理通常會發送 JWT，一般是在 `Authorization` HTTP標頭中使用 `Bearer` 方案。該標頭內容可能如下所示：

```
Authorization: Bearer eyJhbGci''...&lt;snip&gt;...''yu5CSpyHI
```

這是一種無狀態認證機制，因為使用者狀態並不會保存在伺服器記憶體中。伺服器上的受保護路由會檢查 `Authorization` 標頭中的 JWT 是否有效；若存在且有效，使用者便可訪問受保護資源。由於 JWT 是自包含的，所需資訊都已包含其中，因此可減少多次查詢資料庫的需要。

## 標準欄位

代碼｜名稱｜說明

`iss`｜簽發者｜標識簽發 JWT 的主體，例如組織名稱或網站 URL。

`sub`｜主題｜標識 JWT 的主題，例如使用者名稱或帳號編號。

`aud`｜受眾｜標識 JWT 的預期接收方。每個預期會處理該 JWT 的主體都必須在受眾聲明中以某個值標識自己。若處理該聲明的主體在存在 `aud` 聲明的情況下，無法以其中某個值標識自己，則該 JWT 必須被拒絕。

`exp`｜過期時間｜標識 JWT 在該時間及之後不得再被接受處理。其值必須為 NumericDate：即整數或小數，表示自 1970-01-01 00:00:00Z 起經過的秒數。

`nbf`｜生效時間｜標識 JWT 將從何時開始被接受處理。該值必須為 NumericDate。

`iat`｜簽發時間｜標識 JWT 被簽發的時間。該值必須為 NumericDate。

`jti`｜JWT ID｜令牌的區分大小寫唯一標識符，即使在不同簽發者之間也應保持唯一。

`typ`｜令牌類型｜若存在，則必須設為一個已註冊的 IANA 媒體類型。

`cty`｜內容類型｜若使用嵌套簽名或嵌套加密，建議將其設為 `JWT`；否則應省略此欄位。

`alg`｜演算法｜簽發者可以自由設置用於驗證令牌簽名的演算法。然而，一些受支持演算法並不安全。

`kid`｜密鑰 ID｜提示客戶端使用了哪把密鑰來生成令牌簽名。伺服器會將此值與已存檔密鑰匹配，以驗證簽名有效且令牌真實。

`x5c`｜X.509 證書鏈｜一個 RFC4945 格式的證書鏈，對應於生成令牌簽名時所用的私鑰。伺服器將使用此資訊驗證簽名有效且令牌真實。

`x5u`｜X.509 證書鏈 URL｜一個 URL，伺服器可從中獲取與生成令牌簽名所用私鑰對應的證書鏈。伺服器將取得並使用這些資訊驗證簽名真實。

`crit`｜關鍵｜一組伺服器必須理解的標頭列表，只有在理解這些標頭後，才可接受該令牌為有效。

代碼｜名稱｜說明

當前已註冊聲明名稱的列表可由 IANA 的 JSON Web Token Claims Registry 獲取。

## 實現

JWT 已有許多語言與框架的實現，包括但不限於：

- .NET（C#、VB.Net 等）
- C
- C++
- Clojure
- Common Lisp
- Dart
- Elixir
- Erlang
- Go
- Haskell
- Java
- JavaScript
- Julia
- Lua
- Node.js
- OCaml
- Perl
- PHP
- PL/SQL
- PowerShell
- Python
- Racket
- Raku
- Ruby
- Rust
- Scala
- Swift

## 漏洞

JSON Web Token 可包含會話狀態。但如果項目需求允許在 JWT 到期前使會話失效，那麼服務就不能再僅憑令牌本身信任其中的斷言。若要驗證令牌中記錄的會話未被撤銷，則必須將令牌斷言與數據存儲進行比對。這樣一來，令牌就不再是無狀態的，從而削弱了 JWT 的主要優勢。

安全顧問 Tim McLean 曾報告，某些 JWT 函式庫在使用 `alg` 欄位驗證令牌時存在漏洞，最常見的情況是接受了 `alg=none` 的令牌。儘管這些漏洞後來已被修補，McLean 仍建議徹底棄用 `alg` 欄位，以防止類似的實現混淆。儘管如此，現實中仍不斷發現新的 `alg=none` 漏洞；在 2018 至 2021 年間，已有四個CVE與此原因有關。

若設計得當，開發者可以通過採取以下預防措施來應對演算法漏洞：

- 不要僅憑 JWT 標頭驅動驗證流程
- 了解相關演算法（避免只依賴 欄位）
- 使用適當大小的密鑰

2017 年，一些 JWT 函式庫被發現容易受到無效橢圓曲線攻擊。

也有人認為，由於標準中提供了大量不同的加密演算法與選項，JSON Web Token 難以被安全地使用，因此網頁前端與後端都應改用其他替代標準。

## 參考文獻

- jwt.io — 由 Auth0 維護的 JWT 專門網站，提供工具與文檔

## 基本資料

- 狀態：擬議標準
- 首次出版：2010年12月28日
- version_date：2015年5月
- 組織：網際網路工程任務組
- 縮寫：JWT
- 委員會：網際網路工程指導組
- 作者：Michael B. Jones、John Bradley、Nat Sakimura
- 基礎標準：JSON、JSON Web Signature（JWS）、JSON Web Encryption（JWE）
- 領域：數據交換
- 網站： RFC 7519