JSON Web Token 安全性:常見漏洞與修復方法
JWT 是 API 驗證的事實標準,但其表面上的簡潔性隱藏了真實的安全陷阱。設定錯誤的 JWT 曾經導致驗證繞過、權限提升和資料外洩。本指南涵蓋最關鍵的漏洞及其防範方法。
JWT 結構回顧
一個 JWT 由三個以點號分隔的 Base64URL 編碼部分組成:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ.signature
- Header:演算法和 Token 類型
- Payload:宣告(使用者資料、過期時間等)
- Signature:密碼學驗證
使用我們的 JWT 編碼器/解碼器檢查 JWT 結構。
若要對 JWT 結構有基礎認識,請參閱我們的 JWT Token 詳解指南。
重大漏洞
1. 演算法混淆攻擊
JWT 中最危險的漏洞。如果伺服器在未驗證的情況下接受 Token 中的 alg 標頭,攻擊者就可以:
攻擊方式:將演算法從 RS256(非對稱)改為 HS256(對稱),並用伺服器的公鑰簽署偽造的 Token:
// Attacker's forged token
header: { "alg": "HS256", "typ": "JWT" }
payload: { "sub": "admin", "role": "superadmin" }
// Signed with the server's PUBLIC key as the HMAC secret
如果伺服器使用公鑰作為密鑰來驗證 HS256 Token,偽造的 Token 就會通過驗證。
修復方法:始終明確指定預期的演算法:
// WRONG - accepts whatever algorithm the token specifies
jwt.verify(token, key);
// CORRECT - enforce specific algorithm
jwt.verify(token, key, { algorithms: ['RS256'] });
2. None 演算法攻擊
某些函式庫接受 "alg": "none" — 一個沒有簽章的 Token:
// Forged token with no signature
header: { "alg": "none", "typ": "JWT" }
payload: { "sub": "admin", "role": "superadmin" }
signature: "" // empty
修復方法:在正式環境中絕不允許 none 演算法。明確建立允許演算法的白名單。
3. 簽署密鑰強度不足
基於 HMAC 的 JWT(HS256/HS384/HS512)的安全性完全取決於密鑰的強度:
// TERRIBLE - can be brute-forced in seconds
secret = "password123"
// WEAK - dictionary attack vulnerable
secret = "my-jwt-secret"
// STRONG - 256+ bits of randomness
secret = "a3f2b8c9d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0"
修復方法:HMAC 密鑰至少使用 256 位元的密碼學隨機性。更好的做法是使用非對稱金鑰(RS256、ES256),這樣簽署金鑰永遠不需要被分享。
4. 缺少過期時間
沒有設定過期時間的 Token 永遠不會失效——被竊取的 Token 等於永久存取權限:
// WRONG - no expiration
{ "sub": "user123", "role": "admin" }
// CORRECT - short expiration
{
"sub": "user123",
"role": "admin",
"exp": 1705312800,
"iat": 1705309200,
"nbf": 1705309200
}
最佳實踐:存取 Token 的過期時間設為 15-60 分鐘。使用安全儲存的 Refresh Token 來維持長期會話。
5. Payload 中包含敏感資料
JWT Payload 是 Base64URL 編碼的,不是加密的。任何人都可以解碼:
echo "eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ" | base64 -d
# {"sub":"123","role":"admin"}
絕不要包含:密碼、API 金鑰、信用卡號碼、個人資料(身分證號、醫療記錄),或任何機密資訊。
6. Token 儲存(XSS vs CSRF)
| 儲存位置 | XSS 漏洞 | CSRF 漏洞 |
|---|---|---|
| localStorage | 是 | 否 |
| Cookie(未設旗標) | 是 | 是 |
| HttpOnly Cookie | 否 | 是 |
| HttpOnly + SameSite Cookie | 否 | 否 |
建議:將 JWT 存放在 HttpOnly、Secure、SameSite=Strict Cookie 中。這可以防止 JavaScript 存取(XSS 防禦)和跨站請求(CSRF 防禦)。
Set-Cookie: token=eyJ...; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600
安全性檢查清單
- 明確指定演算法 — 絕不接受 Token 標頭中的演算法
- 拒絕
none演算法 — 始終要求有簽章 - 使用強密鑰 — HMAC 至少 256 位元,RSA 至少 2048 位元
- 設定短過期時間 — 存取 Token 15-60 分鐘
- 驗證所有宣告 —
exp、iss、aud、nbf - 存放在 HttpOnly Cookie 中 — 不要用 localStorage
- 定期輪換密鑰 — 規劃金鑰輪換機制
- 絕不在 Payload 中儲存敏感資料
- 分散式系統使用非對稱金鑰(RS256 或 ES256)
- 實作 Token 撤銷機制 — 黑名單或短過期 + Refresh Token
非對稱 vs 對稱簽署
| 面向 | HS256(對稱) | RS256(非對稱) |
|---|---|---|
| 金鑰 | 共享密鑰 | 私鑰/公鑰對 |
| 誰能簽署 | 持有密鑰的任何人 | 僅持有私鑰者 |
| 誰能驗證 | 持有密鑰的任何人 | 持有公鑰的任何人 |
| 金鑰分發 | 密鑰必須安全分享 | 只分享公鑰 |
| 效能 | 較快 | 較慢 |
| 適用場景 | 單一服務 | 微服務、分散式系統 |
建議:新應用程式使用 ES256(ECDSA)——它提供非對稱安全性,且效能接近 HMAC。
Token 撤銷策略
JWT 在設計上是無狀態的——沒有內建的撤銷方法。可用策略:
- 短過期時間:如果 Token 在 15 分鐘內過期,被竊取的 Token 窗口期有限
- Refresh Token 輪換:每次使用時發放新的 Refresh Token;如果一個 Refresh Token 被使用兩次,撤銷所有 Token
- 黑名單:儲存已撤銷的 Token ID(jti 宣告)並在每次請求時檢查
- Token 版本控制:在宣告中包含版本號;在使用者登出時遞增版本
常見問題
我應該使用 JWT 還是 Session Cookie 進行驗證?
Session Cookie 對傳統 Web 應用程式來說更簡單、更安全——伺服器控制 Session 生命週期,撤銷也是即時的。JWT 更適合無狀態 API、微服務和行動應用程式,因為在這些場景中伺服器端 Session 儲存不太實際。如果你選擇 JWT,請實作本指南中的安全措施。
JWT 的理想過期時間是多久?
存取 Token:15-60 分鐘。越短越安全,但需要更頻繁地刷新。Refresh Token:1-30 天,存放在 HttpOnly Cookie 中。一次性 Token(電子郵件驗證、密碼重設):1-24 小時。
相關資源
- JWT 編碼器/解碼器 — 安全地檢查和解碼 JWT
- JWT Token 詳解 — 了解 JWT 結構與流程
- 密碼熵值解析 — JWT 簽署密鑰的強度