Utilo

如何使用JWT以及如何安全解密程式碼

瞭解JSON Web Tokens的結構,如何建立和驗證簽名,常見的索賠,安全陷以及如何解碼JWT。

· 6 分鐘閱讀

JSON Web Token (JWT) 遍佈各地:OAuth和OpenID Connect登入,API閘道器,微服務之間的單一登入,以及移動應用程式會話。它們也被廣泛誤解。開發人員解碼一個令牌,看到可讀JSON,並想知道這是否是安全問題;其他人將敏感資料儲存在令牌中,假設它們是加密的。這篇文章解釋了JWT是什麼,如何驗證,以及如何在不造成風險的情況下檢查。

一個JWT的三個部分

一個JWT看起來像三個隨機串連線的點:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsImV4cCI6MTkwMDAwMDAwMH0.Kx8...

每個部分都是Base64URL編碼:

  1. 頭條 JSON描述令牌型別和簽名演算法,例如{"alg":"HS256","typ":"JWT"}。
  2. 付費負載 JSON 包含索賠:關於使用者和代幣的宣告,如 {"sub":"42","exp":1900000000}。
  3. 簽名 從頭部和有效載荷中計算的位元組,使用秘密或私鑰。

Base64URL是[Base64] (/blog/base64-explained) 的一種變體,它使用 - 和 _ 而不是 + 和 / 並放棄填充,因此可以在 URL 和標題中放置代幣而不會逃脫。

關鍵是,頭條和有效載荷只有編碼,而不是加密。任何持有這個代幣的人都能讀到它們。貼上一個令牌到一個JWT解碼器 並且有效載荷會立即出現。

簽名是如何工作的

簽名是JWT值得信賴的。發行人把加密的頭和有效載荷,透過點連線它們,

在HS256 (HMAC與SHA-256) 中,發行人和驗證者共享一個金鑰:

signature = HMAC-SHA256(secret, base64url(header) + "." + base64url(payload))

透過RS256或ES256,發行人使用私鑰簽名,任何人都可以使用匹配的公鑰驗證,通常在JWKS終端點 (如https://login.example.com/.well-known/jwks.json) 釋出。當許多服務需要驗證代幣,但只有一個應該發行代幣時,不對稱演算法更受歡迎。

當伺服器接收一個令牌時,它會重新計算或驗證簽名。如果有效載荷中的一個字元改變了,例如攻擊者編輯了"role":"user"到"role":"admin",簽名不再匹配,令牌被拒絕。

您應該知道的登記索賠

JWT規範 (RFC 7519) 定義了標準索賠名稱:

  • iss (發行人):誰建立了代幣,例如https://login.example.com。
  • sub (主題):代幣是關於誰的,通常是一個使用者ID。
  • aud (觀眾):該代幣是為誰設計的,例如api.example.com。
  • exp (到期時間):該代幣必須被拒絕後的時間,以[Unix秒] ((/blog/unix-timestamp-explained)。
  • nbf (不是之前):令牌不有效之前的時間。
  • iat (發行時間):當代幣建立時。
  • jti (JWT ID):用於撤銷清單的唯一識別符號。

申請新增自己的權利要求:scope,roles,tenant_id,email。讓他們少;每一個要求都伴隨著每一個請求。

正確驗證一個令牌

伺服器必須按照下列順序檢查:

  1. The algorithm is one you expect. Configure an allow-list such as ["RS256"]。永遠不要讓代幣的頭部自由決定;歷史漏洞讓攻擊者切換到none或欺騙伺服器使用公鑰作為HMAC秘密。
  2. 簽名是有效的 用正確的金鑰,由kid從您所信任的金鑰集中選擇。
  3. exp在未來,nbf在過去,允許小時鐘偏差3060秒。
  4. iss matches your identity provider exactly。
  5. aud包含您服務的識別符號,因此另一個API發行的代幣不能與您的代幣進行重播。

使用一個維護的庫 jose用於JavaScript,PyJWT用於Python,golang-jwt用於Go,而不是自己編寫驗證。

解碼與驗證

解碼意味著Base64URL解碼前兩個部分並解析JSON。它不需要鑰匙,也不證明其真實性。它可用於:

  • 為什麼API返回401:aud是正確的?代幣已經過期了嗎?
  • 檢查登入流實際授予哪些範圍或角色。
  • 證實一個令牌更新產生了一個新的exp。

驗證是指檢查上述簽名和權利要求。只有伺服器才能做出授權決定,而且只有經過驗證。

前端程式碼可能會解碼一個令牌,以顯示使用者的名字或在exp之前安排更新,但它絕不能依賴解碼的安全要求,因為使用者可以修改他們的瀏覽器中執行的任何東西。

在瀏覽器中儲存令牌的地方

沒有完美的答案,只有權衡:

  • HttpOnly,Secure,SameSite cookie不能被JavaScript讀取,這可以透過跨站點指令碼來防止代幣被盜。它們需要CSRF保護,儘管SameSite=Lax或Strict涵蓋大多數情況。
  • 記憶體 (JavaScript變數) 是安全的,但在重新載入時會丟失;將其與HttpOnly Cookie中的重新整理令牌結合起來。
  • localStorage是方便的,但可由頁面上的任何指令碼讀取,因此一個XSS漏洞洩露每個代幣。

對於大多數Web應用程式來說,儲存在記憶體中的短期訪問令牌加上HttpOnly Cookie中的重新整理令牌是一個很好的平衡。

失效和撤銷

JWT是無狀態的:伺服器可以在沒有資料庫查詢的情況下驗證它們。另一方面,有效的代幣在到期之前,。緩解措施:

  • 儲存訪問令牌的時間很短,5至15分鐘是常見的。
  • 使用更新令牌,安全儲存並與資料庫進行檢查,以發行新的訪問令牌。
  • 對於高風險的行動,請檢查jti值的撤銷列表或每個使用者的"代幣有效後"時間。

常見的錯誤

  • 密碼,API金鑰和個人資料可以由任何擁有代幣的人讀取。如果您需要保密,請使用JWE (加密代幣) 或將資料保留在伺服器端。
  • 非常持久的代幣. 一個有效一年的被盜代幣是一年的違規。
  • 跳過觀眾檢查. 如果沒有aud驗證,低特權服務的代幣可以用於高特權服務。
  • **弱HMAC密碼.**HS256密碼必須長且隨機,至少為256位。簡短的秘密可以透過單個代幣進行離線強迫。
  • **日誌令牌.**訪問日誌和錯誤追蹤器通常捕獲Authorization頭。編輯它們。

安全檢查一個代幣

在除錯時,使用在瀏覽器中本地執行的解碼器,以便令牌永遠不會傳送給第三方。在共享截圖或貼上到門票時更喜歡過期或測試環境令牌。檢查:

  • 標題中的alg和kid與您的供應商的配置相匹配。
  • iss和aud符合您的API預期。
  • exp,iat和nbf相對於當前時間是有意義的。
  • 範圍和角色包含操作所需要的內容。

總結

一個JWT是一個簽名,未加密的JSON索賠捆綁。它的安全性完全來自伺服器上的簽名驗證和索賠驗證。解碼是無害的,並且對除錯有用;信任未經驗證的解碼資料是真正的風險。保持代幣的使用壽命短,保持小的有效負載和沒有秘密,每次驗證演算法,簽名,到期,發行人和受眾,

本頁內容由英文自動翻譯而來,如發現錯誤,歡迎告訴我們。

相關指南