跳到主要內容

← 返回部落格

JWT 是什麼?JSON Web Token 如何運作(以及如何解讀)

只要你做過或除錯過登入系統,就會遇到 JWT —— 一個在用戶端與伺服器間傳遞、用來證明「你是誰」的精簡 token。這篇說明它如何運作,以及如何安全地解讀。

你可以自己解一個,不需要密鑰。 把 JWT 官方文件的示範 token 貼進我們的 JWT 解碼器,它會把兩個可讀的部分還給你 —— 標頭 { "alg": "HS256", "typ": "JWT" } 與內容 { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 } —— 而你完全沒提供任何密鑰。這是關於 JWT 最重要的一件事:那些聲明是明文傳輸的。簽章證明的是「這個 token 由持有密鑰的人簽發、之後沒被改過」,那和「機密性」是完全不同的保證。我們的解碼器在你的瀏覽器裡跑,所以你可以放心貼真實 token,它不會離開你的機器。

三個部分

JWT 是三段以點號連接的 Base64URL 字串:header.payload.signature

  • Header(標頭) —— 中繼資料,主要是簽章演算法,例如 {"alg":"HS256","typ":"JWT"}
  • Payload(內容) —— claims,一個關於使用者與 token 的 JSON 物件。
  • Signature(簽章) —— 對 header 與 payload 的密碼學簽章,用密鑰(或私鑰)產生,用來偵測竄改。

你可以用 JWT 解碼器拆開並讀出前兩段。

常見 claims

Payload 裝的是 claims,部分是標準化的:

  • sub —— 主體(通常是使用者 ID)。
  • iss —— 簽發者(誰建立了這個 token)。
  • iat —— 簽發時間(Unix 時間戳)。
  • exp —— 到期時間(Unix 時間戳)。過了就該拒絕這個 token。
  • aud —— 預期的接收對象。

再加上你的應用自訂的 claims(角色、email 等)。

JWT 如何用於驗證

典型的無狀態流程:你登入,伺服器回傳一個已簽章的 JWT,你的用戶端在每次請求都帶著它(通常放在 Authorization: Bearer … 標頭)。伺服器驗證簽章並檢查 exp —— 有效的話,就信任其中的 claims,不必查資料庫。這種無狀態正是它的主要賣點。

是簽章,通常不是加密

多數 JWT 是簽章(JWS),不是加密。簽章證明完整性與真實性,但 payload 只是 Base64URL 編碼 —— 任何人都能讀。所以:

  • 絕不把機密放進 payload(密碼、API 金鑰、隱私資料)。
  • 加密是另一回事(JWE),而且少見得多。

JWT 的大部分困惑就是在這裡解開的,而它是用輸出解開的。我們把 JWT 規格書裡的示範 token 貼進我們自己的 JWT 解碼器,沒有提供任何形式的密鑰。它回傳了標頭

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

與內容

{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}

沒有任何東西被解密,因為本來就沒有東西被加密。這兩段是 Base64url 文字,而簽章保護的是它們不被修改,不是不被閱讀。任何拿到這個 token 的人都讀得到裡面每一項聲明 —— 這就是為什麼 JWT 絕對不該裝任何你不願意放進網址的東西。

真正重要的安全規則

  • 一律在伺服器端用你的密鑰/公鑰驗證簽章。 解碼(像本工具做的)不等於驗證。
  • 檢查 exp 過期的 token 必須拒絕。
  • HS256 vs RS256: HS256 用一組共享密鑰;RS256 用私鑰簽、公鑰驗 —— 當多個服務需要驗證但不簽發時更合適。
  • 絕不把真正的密鑰貼進線上工具。 解碼 token 檢視 claims 沒問題;驗證需要密鑰,應該放在你自己的程式裡。

解讀一個 token

要除錯驗證問題 —— token 過期了嗎?角色對嗎? —— 就解碼它、檢視 payload。免費的 JWT 解碼器會顯示 header 與 payload,並把 exp 換成可讀時間,全程在你的瀏覽器本機進行,token 不會上傳。

這也順便解決了「什麼可以寫進 log」這個實務問題。{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 } 可以印出來 —— 對任何拿到 token 的人來說它本來就是可讀的。但第三段不行:它是唯一能證明前兩段沒被改過的部分,而把完整的 token 貼進 bug 回報,等於在它過期之前把一份可用的憑證交出去。

解碼範例

拿這個廣為流傳的範例 token:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

把前兩段解碼會得到:

// header
{ "alg": "HS256", "typ": "JWT" }
// payload
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }

第三段是簽章 —— 它沒辦法「讀」,只用來證明前兩段沒被更動。

HS256 vs RS256 速覽

HS256RS256
家族HMAC-SHA256(對稱式)RSA-SHA256(非對稱式)
金鑰一組共享密鑰私鑰簽章、公鑰驗證
誰能產生有效 token任何持有密鑰的人只有握有私鑰的人
最適合同一個服務簽發又驗證多個服務驗證、單一服務簽發

處理 exp 與 iat

expiatUnix 時間戳 —— 自 1970-01-01 UTC 起算的「秒」數,不是毫秒。上面的 1516239022 是 2018-01-18。可用 Unix 時間戳轉換器換算;若對這串數字不熟,可看 Unix 時間戳指南

另外兩個安全陷阱

  • 拒絕 alg: none,並固定你預期的演算法 —— 否則攻擊者可以拿掉簽章,或把 RS256 降級成 HS256。
  • JWT 在 exp 之前很難撤銷。 把有效期設短,敏感操作改用 refresh token。

記住:本站的解碼器只「解碼」,從不驗證。在你的伺服器驗過簽章之前,請把「已解碼但未驗證」的 payload 一律當成不可信。