跳至內容
Toova
所有工具

什麼是 JWT 以及如何安全解碼

Toova

JSON Web Token 出現在現代網頁開發的各處 — 認證標頭、OAuth 流程、API 金鑰、會話管理。如果你曾使用過任何需要 Bearer 令牌的 API,你已經使用過 JWT。但多數開發者將它們視為不透明的區塊,鮮少深入。理解 JWT 的結構、每個部分的意義,以及如何在不犯資安錯誤的情況下解碼,約需十五分鐘。本指南涵蓋這一切。

JWT 的結構

每個 JWT 是由三個以點分隔、經 Base64URL 編碼的段組成的字串:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsImlzcyI6Imh0dHBzOi8vYXV0aC5leGFtcGxlLmNvbSIsImF1ZCI6Imh0dHBzOi8vYXBpLmV4YW1wbGUuY29tIiwiZXhwIjoxNzQ3MDAwMDAwLCJpYXQiOjE3NDY5OTY0MDAsInNjb3BlIjoicmVhZDp1c2VycyJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

格式為 header.payload.signature。每個段編碼不同資訊。

標頭

第一段是標頭。解碼後,上述範例變成:

{
  "alg": "RS256",
  "typ": "JWT"
}

alg 指定用於簽署令牌的演算法 — RS256 表示 RSA 搭配 SHA-256。typ 識別令牌類型。這兩個欄位是最低要求;某些令牌也包含 kid(金鑰 ID),告知驗證者在多金鑰輪替時應使用哪個公鑰。

載荷

第二段是載荷 — 實際資料。解碼後:

{
  "sub": "user_123",
  "iss": "https://auth.example.com",
  "aud": "https://api.example.com",
  "exp": 1747000000,
  "iat": 1746996400,
  "scope": "read:users"
}

載荷包含「宣告」:關於令牌所代表實體的陳述。某些宣告是標準化的(稱為註冊宣告);其他則是應用特定的(稱為私有宣告)。載荷是 Base64URL 編碼,並未加密。任何取得令牌字串的人都能讀取其中每個宣告。

簽章

第三段是簽章。它由取得編碼後的標頭與載荷、以點串接,然後使用 alg 中指定的密鑰或私鑰簽署該結果產生:

signature = sign(
  base64url(header) + "." + base64url(payload),
  secretOrPrivateKey
)

簽章讓任何擁有對應公鑰(對於對稱演算法則為密鑰)的一方,能驗證令牌由受信任來源頒發,且標頭與載荷在頒發後均未被修改。載荷中只要有一個字元變更,簽章就會完全失效。

如何解碼 JWT

解碼與驗證不同。解碼只是讀取令牌內的資料。驗證則確認令牌是真實且未過期。你在生產程式碼中應永遠驗證;單獨解碼僅對除錯有用。

步驟 1:分割令牌

. 字元分割 JWT 字串。你會得到三段。如果你得到少於三段,令牌格式錯誤。

步驟 2:解碼標頭與載荷

對前兩段分別進行 Base64URL 解碼。Base64URL 類似標準 Base64,但使用 - 取代 +_ 取代 /,且無填充字元。解碼後,你可以將每段解析為 JSON。

步驟 3:解析與檢查

讀取宣告。對照目前 Unix 時間戳檢查 exp(過期)。查看 issaud 以確認令牌是為你的應用而頒發。在驗證簽章前,不要信任任何宣告。

開發中若需快速檢查,請使用 JWT 解碼器。它完全在你的瀏覽器中執行 — 你的令牌絕不離開裝置。若你想看到原始位元組,也可以用 Base64 編碼/解碼器手動解碼 base64 段。若要在測試腳本中驗證簽章,HMAC 產生器讓你無需撰寫程式碼就能重現 HS256 簽章。

步驟 4:在程式碼中驗證簽章

在生產環境中,請永遠使用為你語言打造的函式庫驗證簽章。不要手動實作簽章驗證。熱門選項包括 Node.js 的 josejsonwebtoken、Python 的 PyJWT、Go 的 golang-jwt/jwt,以及 Java 的 nimbus-jose-jwt

永遠明確傳入演算法。切勿讓函式庫從令牌標頭推斷演算法。

標準宣告(註冊宣告)

RFC 7519 定義一組註冊宣告名稱。這些並非必要,但當存在時必須遵循標準語意。

iss — 簽發者

建立並簽署令牌的實體。通常是識別授權伺服器的 URL(例如,https://auth.example.com)。驗證者應檢查 iss 是否與預期值相符,並拒絕來自未知簽發者的令牌。

sub — 主體

令牌所代表的主體 — 通常是使用者 ID、服務帳戶名稱或裝置識別碼。值必須在簽發者的情境中唯一。你的應用使用 sub 識別請求屬於哪位使用者。

aud — 受眾

令牌的預期接收者。如果你的 API 識別碼是 https://api.example.com,為其他服務頒發的令牌應被拒絕。未驗證 aud 會讓攻擊者能將從一個服務取得的令牌在不同服務上重複使用。

exp — 過期時間

過此 Unix 時間戳後令牌不得被接受。請永遠驗證 exp。沒有過期時間的令牌實質永久有效 — 若被竊,攻擊者就有無限期存取。

iat — 簽發時間

令牌建立時的 Unix 時間戳。對偵測技術上未過期但可疑陳舊的令牌很有用。你可以使用 iat 強制執行獨立於 exp 的最大令牌期限。

nbf — 不早於

此 Unix 時間戳之前令牌不得被接受。比 exp 較不常見,但在預先頒發令牌時很有用 — 例如,排定的工作不應在特定時間之前啟動。

jti — JWT ID

令牌的唯一識別碼。讓簽發者能透過儲存已使用的 jti 值並拒絕具重複 ID 的令牌,來防止令牌重播攻擊。當你需要一次性使用令牌時必要,例如密碼重設連結。

資安陷阱

JWT 實作歷史上有許多微妙漏洞。以下是發布接受 JWT 程式碼前最重要理解的內容。

「alg: none」攻擊

標準早期的某些 JWT 函式庫會接受標頭中指定 "alg": "none" 的令牌。攻擊者可取得任何有效令牌、將演算法替換為 none、去除簽章,而該函式庫會接受其為完全有效 — 不需要簽章。

修正:呼叫驗證函數時永遠明確指定預期演算法。將任何宣稱 alg: none 的令牌視為無效。多數現代函式庫已解決此問題,但在進入生產環境前確認你函式庫的預設值是值得的。

演算法混淆(RS256 至 HS256 降級)

支援兩種演算法的某些函式庫會使用令牌標頭中的 alg 欄位決定如何驗證。攻擊者可將演算法變更為 HS256,然後使用伺服器的「公鑰」作為 HMAC 密鑰簽署令牌。伺服器看到 HS256,會以公鑰驗證並接受偽造令牌。

修正方式相同:在你的驗證程式碼中鎖定演算法。切勿讓令牌標頭影響用於驗證的演算法。

接受已過期令牌

不驗證 exp 是常見的疏漏 — 有時是因為開發者新增無邊界增長的寬限期,或驗證程式碼路徑在某個分支被略過。將已過期令牌視為與遺漏令牌相同:以 401 拒絕,要求客戶端重新認證或使用更新令牌。

載荷中的敏感資料

載荷是 Base64URL 編碼,並未加密。任何攔截到令牌的人都能解碼它。不要在載荷中放置密碼、信用卡號碼、社會安全號碼或其他敏感資料。如果你需要安全傳輸敏感宣告,請使用 JWE(JSON Web Encryption)令牌,它會加密載荷。以 JWS 簽署的 JWT(常見情況)僅保證真實性,不保證機密性。

遺漏受眾驗證

略過 aud 驗證代表為服務 A 頒發的令牌可在服務 B 上重播 — 只要兩個服務共享相同簽署金鑰或信任相同簽發者。在多服務架構中,請永遠驗證令牌的受眾是否與你服務的識別碼相符。

2026 年最佳實踐

多數應用選擇 RS256

RS256 使用私鑰簽署、公鑰驗證。只有授權伺服器持有私鑰。任何服務都能使用公鑰驗證令牌,公鑰可公開發布(通常透過 JWKS 端點)。如果任何個別服務被入侵,攻擊者只取得驗證令牌的能力 — 但無法偽造新令牌。

HS256 使用單一共享密鑰進行簽署與驗證。任何能驗證令牌的服務也能建立令牌。在微服務設定中,跨多個服務共享密鑰會增加違規的影響範圍。

讓存取令牌保持短效

JWT 沒有內建撤銷機制 — 一旦頒發,令牌就有效到過期(除非你實作封鎖名單,但這會重新引入伺服器端狀態)。短過期時間(15 分鐘至 1 小時)能限制令牌被竊時的損害視窗。將存取令牌與儲存於安全 HttpOnly cookies 中的長效更新令牌搭配使用,而非儲存於 localStorage

使用更新令牌輪替

當客戶端使用更新令牌取得新的存取令牌時,頒發新的更新令牌並使舊令牌失效。這樣,被竊更新令牌會在合法客戶端下次嘗試使用原始令牌時被偵測到:伺服器看到重複使用的更新令牌,可撤銷整個會話。此模式描述於 RFC 6819,並受現代授權伺服器廣泛支援。

發布 JWKS 端點

JSON Web Key Set(JWKS)端點(慣例為 /.well-known/jwks.json)以標準格式發布你的簽署公鑰。其他服務可擷取 JWKS 並驗證令牌,而不需要透過頻外管道取得金鑰。這也讓金鑰輪替變得簡單:將新金鑰新增至 JWKS、開始用它簽署,然後在所有現有令牌過期後移除舊金鑰。

定期輪替金鑰

即使你的私鑰從未被入侵,定期輪替金鑰也能限制被竊金鑰用於偽造令牌的視窗。在 JWT 標頭中使用 kid 引用簽署金鑰,讓驗證者能從你的 JWKS 中選擇正確的公鑰,而不會在輪替期間破壞現有令牌。

驗證每個宣告

每個請求都檢查 issaudexpnbf。不要因為在你的設定中看起來多餘就跳過任何一個 — 讓它們看似不必要的條件,正是事件發生時會變更的條件。

JWT vs 會話 Cookie

JWT 與伺服器端會話解決相同問題 — 在無狀態 HTTP 中持續認證狀態 — 但取捨不同。

JWT 自我包含。伺服器不需要查詢資料庫即可驗證請求。這讓它們對分散式系統,以及由行動或第三方客戶端使用的 API 有吸引力。缺點是撤銷需要封鎖名單(會重新引入伺服器端狀態),或容忍被入侵令牌的剩餘存活期。

會話 Cookie 由伺服器驗證。伺服器儲存會話狀態,並能透過刪除會話即時撤銷存取權。具 HttpOnlySecure 旗標的 Cookie 受保護免於 JavaScript 存取與網路攔截。缺點是每個請求都需要資料庫查詢,在規模上可能成為瓶頸。

對具伺服器渲染頁面與單一後端的傳統網頁應用,會話 Cookie 仍是堅實且較簡單的選擇。對由多個客戶端使用的 API、微服務架構與行動應用,具短過期與更新令牌輪替的 JWT 是較實務的選項。

解碼你的令牌

檢查 JWT 最快的方式是將它貼入 JWT 解碼器。它會分割令牌、解碼標頭與載荷,並以可讀格式呈現宣告 — 全部不將你的令牌傳送至任何地方。如果你需要手動編碼或解碼原始 Base64URL 字串,Base64 編碼/解碼器同時處理標準與 URL 安全變體。對於在測試腳本中驗證 HMAC 簽章,HMAC 產生器讓你能重現 HS256 簽章並對照你令牌中的內容比較。

完整規格請見 RFC 7519(JWT),以及 jwt.io 的互動範例。