你見過它們 — 長長的一串由三個用點分隔的塊組成的字串貼在 Slack、偵錯日誌和各處的 API 玩具中。JWT token 是現代認證的骨幹,但大多數開發者將它們視為不透明的塊。讓我們來打開一個吧。
JWT 其實是什麼
JWT (JSON Web Token) 是三個 Base64URL 編碼的塊由點連接起來:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiJ1c2VyXzEyMyIsIm5hbWUiOiJKYW5lIERvZSIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTcxNjIzMDAwMCwiZXhwIjoxNzE2MjMzNjAwfQ
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
這三個部分是:標頭,有效載荷,簽名。
標頭
使用atob()(或任何 Base64 解碼器)解碼第一個片段,你會得到:
{
"alg": "HS256",
"typ": "JWT"
}
alg 會告知伺服器用於簽署權杖的演算法。常見值:
-
HS256— HMAC-SHA256 (對稱, 共享密鑰) -
RS256— RSA-SHA256 (非對稱, 公開/私密金鑰對) -
ES256— ECDSA-SHA256 (非對稱, 椭圓曲線)
none 在技術上也是有效的 — 而且在歷史上當伺服器接受它時曾造成嚴重的漏洞。
載荷 (有趣的部分)
載荷包含 聲明 — 關於使用者及令牌本身的陳述:
{
"sub": "user_123",
"name": "Jane Doe",
"role": "admin",
"iat": 1716230000,
"exp": 1716233600
}
標準註冊聲明:
| 聲明 | 名稱 | 意義 |
|---|---|---|
sub |
主體 | 此令牌是關於誰(通常是使用者 ID) |
iss |
發行人 | 誰創建並簽署了此令牌 |
aud |
受眾 | 此令牌是為誰設計的 |
exp |
過期時間 | Unix 時間戳 — 如果過期則拒絕 |
nbf |
生效起始時間 | 此時間戳之前令牌無效 |
iat |
發行時間 | 當令牌被創建時 |
jti |
JWT ID | 令牌的唯一識別碼 |
自定義聲明如 role、email、permissions 皆為應用程式特定且完全有效
注意: 載荷是 Base64URL 編碼的,並非加密。任何擁有 token 的人都可讀取這些聲明。切勿將密碼、信用卡號碼或其他機密信息放在載荷中。
簽名
簽名是這樣計算的:
HMAC-SHA256(
base64url(header) + "." + base64url(payload),
secret_key
)
伺服器使用這個來驗證令牌沒有被竄改。如果你在有效負載中更改一個字符,簽名就無法匹配,令牌就會被拒絕。
這就是 JWT 有用的原因 — 伺服器不需要對每個請求進行資料庫查詢。它僅僅是數學上驗證簽名。
沒有使用庫讀取 JWT
在 JavaScript 中:
function decodeJwt(token) {
const [header, payload] = token.split('.');
const decode = (str) => JSON.parse(atob(str.replace(/-/g, '+').replace(/_/g, '/')));
return {
header: decode(header),
payload: decode(payload)
};
}
The - → + 和 _ → / 的替換將 Base64URL 回復至標準 Base64,atob() 接受。
在 Python:
import base64, json
def decode_jwt(token):
parts = token.split('.')
# Pad to multiple of 4
payload = parts[1] + '=='
return json.loads(base64.urlsafe_b64decode(payload))
到期陷阱
exp 是一個 Unix 時間戳 (自 1970-01-01 UTC 開始的秒數)。像這樣檢查它:
const { exp } = decodeJwt(token).payload;
const isExpired = Date.now() / 1000 > exp;
一個常見的錯誤:因為忘了檢查 exp 而信任過期的令牌。像 jsonwebtoken (Node.js) 和 PyJWT (Python) 這樣的 JWT 庫在您調用其驗證函數時會自動檢查過期時間 — 但原始解碼則不會。
演算法混亂攻擊
永遠不要接受alg: none在您的伺服器上。這是早期 JWT 庫中的一個真正漏洞——攻擊者可以刪除簽名並設置alg到none,而且有些伺服器會接受偽造的 token 作為有效。
類似地,如果您的伺服器使用非對稱 RS256(公/私鑰),攻擊者可能會嘗試發送一個 token 來alg: HS256 和使用您的公鑰作為 HMAC 秘密對其進行簽名。始終明確指定預期的算法:
jwt.verify(token, publicKey, { algorithms: ['RS256'] });
在瀏覽器中快速解碼
如果您正在調試身份驗證問題並需要快速檢查令牌包含哪些聲明,將其粘貼到這個 JWT 解碼器中— 它在客戶端解碼標頭和有效負載,無需伺服器上傳,突顯過期狀態,並標記所有標準聲明。
無狀態驗證及其妥協
JWTs 在驗證上的吸引力在於其無狀態性 — 伺服器將其所需的所有資訊編碼到令牌中並簽署,因此每個請求都不需要進行會話查找。這使得它能在多個伺服器間無縫擴展,而無需共享會話存儲。
取捨:你無法在 JWT 標準到期前撤銷它,除非維護一個封鎖清單(這會重新引入狀態)。標準緩解措施:
- 短期過期 (15 分鐘) + 刷新憑證 (更長壽命,存儲在伺服器端)
- 撤銷清單 用於關鍵事件 (登出、密碼更改、賬戶封禁)
- 令牌旋轉 — 每次使用刷新令牌時發行新的訪問令牌
JWS 與 JWE
你幾乎總是看到的是 JWS — JSON Web Signature。有效負載是可讀的;簽名防止篡改。
JWE (JSON Web Encryption) 完全加密有效負載,並產生一個五部分的令牌。當 JWT 本身必須攜帶不應該無密鑰讀取的敏感數據時使用 JWE — 大多數認證令牌不需要這個。
JWT 令牌一旦看到其三部分結構就比看起來簡單得多。魔法都在簽名上 — 一個微小的加密證明,證明聲明自服務器發出令牌以來沒有改變。










