如何驗證 JWT 簽名:HS256、RS256 與 ES256
如何驗證 JWT 簽名
JWT 以明文形式攜帶其聲明(claims)。唯一防止偽造的就是簽名。若不驗證,攻擊者可以偽造任意權杖並冒充任何使用者。本指南涵蓋三種最常見的 JWT 簽名演算法以及每種演算法的驗證方式。
驗證流程
無論使用哪種演算法,JWT 簽名驗證都遵循相同的步驟:
- 以
.分割權杖,提取標頭、有效載荷和簽名。 - 對標頭進行 Base64url 解碼,讀取
alg欄位。 - 使用該演算法和金鑰,根據標頭和有效載荷重新計算簽名。
- 將重新計算的簽名與權杖中的簽名進行比對。
- 如果相符,則權杖是真實的。
HS256(HMAC-SHA256)
HS256 是一種對稱演算法 — 同一個密鑰用於建立和驗證簽名。
signature = HMACSHA256(
base64urlEncode(header) + "." + base64urlEncode(payload),
secret
)
要進行驗證,使用共享密鑰重新計算 HMAC 並進行比對:
Token: header.payload.sig
Secret: "my-shared-secret"
1. Compute: HMACSHA256("header.payload", "my-shared-secret")
2. Compare: computedSignature === sig
安全考量: 密鑰必須保密、定期輪換,且至少具有 256 位元的熵。HS256 不適合微服務架構,因為許多服務都需要共享密鑰。
RS256(RSA-SHA256)
RS256 是非對稱的。簽發者使用私鑰簽名;驗證者使用對應的公鑰進行檢查。
signature = RSASHA256(
base64urlEncode(header) + "." + base64urlEncode(payload),
privateKey
)
要進行驗證,使用公鑰:
Token: header.payload.sig
Public: "-----BEGIN PUBLIC KEY-----..."
1. Load the RSA public key
2. Verify: RSA_SHA256_verify("header.payload", sig, publicKey)
優勢: 簽名金鑰和驗證金鑰是分離的。任何服務都可以進行驗證而無需持有密鑰。公鑰可以通過 JWKS 端點提供,以便輕鬆輪換。
ES256(ECDSA-SHA256)
ES256 使用 P-256 曲線的 ECDSA。與 RS256 一樣,它是非對稱的。
signature = ECDSA_SHA256(
base64urlEncode(header) + "." + base64urlEncode(payload),
privateKey
)
驗證使用 ECDSA P-256 公鑰。ES256 產生的簽名要小得多(64 位元組 vs RSA-2048 的 256 位元組),且驗證速度更快。
演算法比較
| 屬性 | HS256 | RS256 | ES256 | |------|-------|-------|-------| | 金鑰類型 | 對稱(共享密鑰) | 非對稱(RSA 金鑰對) | 非對稱(EC 金鑰對) | | 簽名大小 | 32 位元組 | 256 位元組 | 64 位元組 | | 金鑰分發 | 秘密共享 | 公鑰公開 | 公鑰公開 | | 輪換 | 中等 | 簡單(JWKS) | 簡單(JWKS) | | 使用場景 | 受信任的內部系統 | 分散式 API | 行動裝置、物聯網 |
常見驗證陷阱
- 演算法混淆攻擊。 如果你的程式碼不加限制地從權杖中讀取
alg,攻擊者可以更改演算法。務必驗證演算法是否與預期值相符。 - 接受
alg: "none"。 某些程式庫會接受未簽名的權杖。請明確拒絕alg: "none"。 - 簽名截斷。 非常數時間的比對可能通過時間攻擊洩漏資訊。請使用你的 JWT 程式庫內建的比對功能。
線上驗證 JWT 權杖
使用 JWT 解碼器工具來解碼和驗證任何權杖。貼上權杖,查看標頭和有效載荷,並通過提供對應的密鑰或公鑰來驗證簽名。所有處理都在你的瀏覽器中完成。