登入後由伺服器交給你的帶簽章權杖,就是 JWT。本文講解它的原理以及安全使用的關鍵要點(不會寫出攻擊手法)。
原理(三個部分)
JWT 是用 . 把 標頭.酬載.簽章 三部分連接而成的字串。標頭和酬載只是用 base64url 編碼(並非加密),只有簽章是用金鑰(或共用金鑰)生成的。
① 標頭
簽章演算法等元資訊。任何人都能讀
② 酬載
使用者 ID、有效期等主張。任何人都能讀=不要放機密
③ 簽章
用金鑰生成。保證未被竄改
內容「只是可讀」這一點至關重要。所以酬載不是放密碼或個人資訊的地方。JWT 的價值在於:伺服器透過校驗簽章,就能確認「這個權杖是我簽發的、且未被竄改」。
安全使用的防禦措施
必須校驗簽章,並固定 alg(最重要)
在伺服器端每次都校驗簽章。把允許接受的簽章演算法(alg)固定為期望的值,並拒絕 alg:none(無簽章)。不要輕信權杖標頭裡寫的 alg。
不要把機密放進酬載
要以"內容人人都能解碼"為前提。密碼、個人資訊、API 金鑰等機密不要放進去。只放識別碼或權限等即便外洩也不致命的主張。
把有效期設短,並準備失效手段
存取用權杖要設為短命。過長的有效期會延長權杖被盜後的受害時間。如果設計上需要失效,就把短命的存取權杖+伺服器端管理的刷新/工作階段搭配使用。
使用強金鑰,並存放在不會被盜的地方
簽章金鑰要足夠強,不要重複使用,並保管在伺服器端的安全位置。權杖本身也要連同保管與傳輸一起守護,比如不被 XSS 竊取、用帶安全屬性的 Cookie 傳輸(→ 注意經由 XSS 的竊取)。
本站觀點:JWT 並不適合當作所有用途的工作階段機制
JWT 常被用於「保存登入狀態」,但它有一個弱點:不易讓它失效。只要簽章正確且在有效期內,伺服器基本上就會信任它,因此很難讓登出或失效立即生效。
本站的立場是:不要把 JWT 當作長期有效的工作階段替代品。把短命的存取權杖,與伺服器端可撤銷的刷新/工作階段組合起來,就能兼顧 JWT 的優點(校驗輕量)和失效的便利性。
注意:「能解碼」不等於「正確」
JWT 的內容是可讀的,所以只要貼進 JWT 解碼器,任何人都能查看標頭和酬載。但是解碼(讀取內容)與校驗(確認真偽)完全是兩回事。能解碼並不能保證任何正確性。判定權杖真偽的,永遠是伺服器端的簽章校驗。除錯時查看內容很方便,但請不要誤以為「能讀出來就有效」。
JWT 與伺服器端工作階段(Cookie)的比較
JWT 只是保存登入狀態的方法之一,有些情況比伺服器端工作階段適合,有些情況則不然。
| 項目 | 伺服器端工作階段(Cookie) | JWT |
|---|---|---|
| 狀態存在哪裡 | 伺服器上(DB 或記憶體);Cookie 只放 ID | 權杖內;伺服器只校驗簽章 |
| 登出/停權 | 在伺服器上刪除即立即生效 | 除非另外建立撤銷機制,否則在到期前都會被接受 |
| 內容的可見性 | 只有 ID,沒什麼可讀 | 任何人都能解碼讀取 |
| 適合的情況 | 單一網域上的一般 Web 應用 | 多個服務或 API 校驗同一個權杖 |
只在單一網域上運作的 Web 應用,伺服器端工作階段往往就足夠。如果選擇 JWT,在登入時發出全新的權杖,與防範工作階段固定是同一個原則。
實際案例:沒有檢查演算法的函式庫
演算法的切換並不只是理論。被廣泛使用的 Node.js JWT 函式庫 jsonwebtoken,在 4.2.1 以前的版本沒有正確檢查權杖標頭中指定的演算法,導致校驗可被繞過(CVE-2015-9235,已在 4.2.2 修正)。原本該用公開金鑰校驗的權杖,改用共用金鑰(HS 系列)演算法簽章後仍會通過——正是上面所說的演算法混淆。
兩個教訓:
- 保持函式庫為最新版本。把 lockfile 貼到相依套件漏洞掃描器,就能確認相依套件中的已知漏洞
- 不要依賴函式庫的預設值,在自己的程式碼中明確寫出允許的演算法
如何檢查自己的權杖
把自己的服務發出的權杖貼到 JWT 解碼器,查看以下各點。解碼在瀏覽器中執行,權杖不會被送到伺服器。即便如此,若是正式環境中仍有效的權杖,事後登出讓它失效比較妥當。
- 標頭的
alg是否為預期的值(不是none)? - 是否有
exp,從iat(簽發時間)算起的有效期是否符合用途? - 是否有
aud與iss,並且指向你的服務? - 酬載中是否含有不想被看到的東西——電子郵件地址、電話號碼、API 金鑰?
伺服器端的校驗,請在開發環境用測試確認。簽章被改了一個字元的權杖、alg 不同的權杖、已過期的權杖,以及給其他服務用的權杖(aud 不符)都應該被拒絕——把這些保留為自動化測試。
出處(一手資料)
- IETF RFC 8725, "JSON Web Token Best Current Practices" — datatracker.ietf.org(固定演算法、校驗所有密碼學運算、演算法混淆、金鑰屬於簽發者)
- IETF RFC 7519, "JSON Web Token (JWT)" — datatracker.ietf.org(
exp、nbf、aud的規範定義) - GitHub Advisory Database, "Verification Bypass in jsonwebtoken"(CVE-2015-9235)— github.com/advisories(4.2.1 以前受影響;4.2.2 修正)
- OWASP HTML5 Security Cheat Sheet — cheatsheetseries.owasp.org(不要把工作階段識別碼存進 localStorage)
接下來閱讀
- 工具:JWT 解碼器 / 檢查(查看內容、檢查 alg:none 或是否過期)
- 設計:密碼重設的設計缺陷(英文;簽發權杖一方的常見錯誤)
- 術語:XSS 是什麼(竊取權杖的典型路徑)/ CSRF 是什麼 / 工作階段固定是什麼 / 公開金鑰密碼學是什麼
- 入門:如何選擇雙因素認證(MFA)