Intro
JWT (JSON Web Token) 是一種基於 JSON 的開放標準(RFC 7519),它定義了一種簡潔(compact)且自包含(self-contained)的方式,用於在雙方之間安全地將訊息作為 JSON 物件傳輸。而這個訊息是經過數位簽章(Digital Signature),因此可以被驗證及信任。可以使用 密碼(經過 HMAC 演算法) 或用一對 公鑰/私鑰(經過 RSA 或 ECDSA 演算法) 來對 JWT 進行簽章。
- RFC 7515 - JSON Web Signature(JWS)
JWT 的簽名標準,包括 Header、Payload 和 Signature,Header 指定了所使用的算法和相關的參數。 - RFC 7516 - JSON Web Encryption(JWE)
加密 JWT Payload 的標準,包括 Header、Public key、IV、Ciphertext 和 Tag,Header 指定了所使用的加密算法和相關的參數。 - RFC 7517 - JSON Web Key(JWK)
用於描述加密和簽章金鑰的 JSON 格式,可以表示對稱式或非對稱式加密、簽章的金鑰。 - RFC 7518 - JSON Web Algorithms(JWA)
定義用於 JWS 和 JWE 中的加密、解密和簽名算法的標準 - RFC 7519 - JSON Web Token(JWT)
JWT 定義為 JWS 或 JWE 的 Compact Serialization 表現形式,本質上只是 JWS 或 JWE 的一種封裝方式。(實務上,提到 JWT 通常是指 JWS)
應用情境
- 授權 - Client-Server Authorization:
這是 JWT(JWS)最常見的應用方式之一。例如使用者在 Client 端成功登入後,Server 會簽發一組 JWT。之後使用者每次對 Server 發送請求時,會附上這個 JWT(通常放在Authorization: Bearer
header 中)。Server 則使用預先設定的 secret key 驗證簽章,從而確認使用者身分與授權。
此方式可達成無需儲存使用者狀態的 Stateless 架構(無需使用 session),Server 端可以僅需用一組 key 簽發並驗證所有 JWT。
此外,JWT 也常被應用於單一登入(Single Sign-On, SSO),其優勢在於實作成本低,且特別適合跨網域(cross-domain)授權場景。
(Reference: ByteByteGoHq/system-design-101 - Github)
-
驗證 - Server-to-Server Authorization:
在 Server 之間進行授權或資料交換時,可以使用 JWT(JWS)作為驗證機制。雙方事先約定一組共同的 secret key(對稱式加密),用於對 JWT 進行簽章與驗證,以確保傳送資料的完整性與來源可信性。
實作上,可以將 API 規格中的主要 body 資料作為 JWT 的 payload 進行簽章以利接收方驗證,JWT 可以直接定義在 request body 中。 -
訊息交換 (Information Exchange):
JWT(JWS)可用於雙方之間的訊息交換,其簽章機制能確保訊息的完整性與來源可靠性。簽章可使用對稱式金鑰(如 HS256)或非對稱式的公私鑰對(如 RS256)。
JWE(JSON Web Encryption)則應用於保障訊息內容的機密性,透過資料加密傳輸來確保訊息的安全性。
資料結構與格式
JWT
根據定義,JWT 僅是 JWS 或 JWE 的 Compact Serialization 表現形式。
然而,在實務上,提到 JWT 通常預設是指 JWS,例如多數線上工具預設僅支援簽章驗證而非解密。
傳送交換格式:
JWTs are always represented using the JWS Compact Serialization or the JWE Compact Serialization.
JSON Web Token (JWT) RFC-7519
JWS
JWS Compact Serialization:
BASE64URL(UTF8(JWS Protected Header)) || '.' ||
BASE64URL(JWS Payload) || '.' ||
BASE64URL(JWS Signature)
Example code:
header = { "alg": "HS256", "typ": "JWT" }
payload = { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
signature = HMAC-SHA256(base64UrlEncode(header) + '.' + base64UrlEncode(payload), secret)
JWS = base64UrlEncode(header) + '.' + base64UrlEncode(payload) + '.' + base64UrlEncode(signature)
JWS JSON Serialization - General & Flattened:
適用於有多組 signatures,或希望以結構化 JSON 方式傳遞簽章資料。
{
"payload": "base64urlPayload",
"signatures": [
{
"protected": "base64urlProtectedHeader",
"signature": "base64urlSignature"
},
{
"protected": "base64urlAnotherHeader",
"signature": "base64urlAnotherSignature"
}
]
}
JWE
JWE Compact Serialization
BASE64URL(UTF8(JWE Protected Header)) || '.' ||
BASE64URL(JWE Encrypted Key) || '.' ||
BASE64URL(JWE Initialization Vector) || '.' ||
BASE64URL(JWE Ciphertext) || '.' ||
BASE64URL(JWE Authentication Tag)
Example code:
header = base64UrlEncode({ "alg": "RSA-OAEP", "enc": "A256GCM", "kid": "5saf8g" })
encryptedSecretKey = RSA-OAEP(publicKey, contentEncryptionKey) // CEK (Content Encryption Key)
iv = generateRandomIV()
cipherText, tag = AES-256-GCM_Encrypt(plaintext, contentEncryptionKey, iv, aad = header)
JWE = base64UrlEncode(header) + '.' +
base64UrlEncode(encryptedSecretKey) + '.' +
base64UrlEncode(iv) + '.' +
base64UrlEncode(cipherText) + '.' +
base64UrlEncode(tag)
JWE JSON Serialization
適用於有多組 recipients,或希望以結構化 JSON 方式傳遞加密資料。
{
"protected": "base64urlProtectedHeader",
"recipients": [
{
"header": { "alg": "RSA-OAEP", "kid": "1234" },
"encrypted_key": "base64urlEncryptedKey1"
},
{
"header": { "alg": "RSA-OAEP", "kid": "5678" },
"encrypted_key": "base64urlEncryptedKey2"
}
],
"iv": "base64urlIV",
"ciphertext": "base64urlCiphertext",
"tag": "base64urlTag"
}
指定 Direct Encryption (alg: "dir")
當 alg: "dir"
時,表示使用 Direct Encryption 模式。這意味著雙方直接使用事先約定好的對稱式金鑰進行加解密,而不需要額外產生並加密一組對稱式金鑰來進行訊息加密與交換。
根據 RFC 7516 規範,在此模式下的 JWE Compact Serialization 應如下
BASE64URL(Protected Header) || '.' ||
"" || '.' || // Empty Encrypted Key
BASE64URL(IV) || '.' ||
BASE64URL(Ciphertext) || '.' ||
BASE64URL(Authentication Tag)
When Direct Key Agreement or Direct Encryption are employed, let the JWE Encrypted Key be the empty octet sequence.
RFC7516 - 5.1. Message Encryption
JWK - JSON Web Key
JWK(JSON Web Key)用於表示加密金鑰的結構與格式,至於實際使用的加密演算法,則由 JWA(JSON Web Algorithms)所規範。
Key 結構格式
以下以 LINE Developer 文件中的 JWK 為例說明:
private.key
example:
{
"alg": "RS256",
"d": "GaDzOmc4......",
"dp": "WAByrYmh......",
"dq": "WLwjYun0......",
"e": "AQ......",
"ext": true,
"key_ops": [
"sign"
],
"kty": "RSA",
"n": "vsbOUoFA......",
"p": "5QJitCu9......",
"q": "1ULfGui5......",
"qi": "2cK4apee......"
}
public.key
example:
{
"alg": "RS256",
"e": "AQ......",
"ext": true,
"key_ops": [
"verify"
],
"kty": "RSA",
"n": "vsbOUoFA......"
}
JWK 應用情境
在 LINE Message API 中,Issue channel access token v2.1 的流程會使用到 JWK 格式:
- Developer 自行產生一組 Key Pair(公開金鑰與私密金鑰)。
- 將公開金鑰(Public Key)以 JWK 格式上傳到 LINE Developer Console,綁定至對應的 Channel。註冊成功後,LINE 會為該組公開金鑰產生一組 kid(Key ID)。
- 之後,當 Developer 要取得 Channel Access Token 時,使用該組私密金鑰(Private Key)來簽章 payload 產生一個 JWT(實際為 JWS)。並在 JWT Header 中指定 kid,讓 LINE Server 知道要用哪一組 Public Key 驗章。
- LINE Server 驗證該 JWS 簽章正確後,即回傳 Channel Access Token。
SDK 開發套件
PHP
- Firebase JWT: https://github.com/firebase/php-jwt
- Lcobucci JWT: https://github.com/lcobucci/jwt
Python
- PyJWT: https://github.com/jpadilla/pyjwt
- python-jose: https://github.com/mpdavis/python-jose