如何使用JWT以及如何安全解密程式碼
瞭解JSON Web Tokens的結構,如何建立和驗證簽名,常見的索賠,安全陷以及如何解碼JWT。
· 6 分鐘閱讀
JSON Web Token (JWT) 遍佈各地:OAuth和OpenID Connect登入,API閘道器,微服務之間的單一登入,以及移動應用程式會話。它們也被廣泛誤解。開發人員解碼一個令牌,看到可讀JSON,並想知道這是否是安全問題;其他人將敏感資料儲存在令牌中,假設它們是加密的。這篇文章解釋了JWT是什麼,如何驗證,以及如何在不造成風險的情況下檢查。
一個JWT的三個部分
一個JWT看起來像三個隨機串連線的點:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsImV4cCI6MTkwMDAwMDAwMH0.Kx8...
每個部分都是Base64URL編碼:
- 頭條 JSON描述令牌型別和簽名演算法,例如
{"alg":"HS256","typ":"JWT"}。 - 付費負載 JSON 包含索賠:關於使用者和代幣的宣告,如
{"sub":"42","exp":1900000000}。 - 簽名 從頭部和有效載荷中計算的位元組,使用秘密或私鑰。
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。讓他們少;每一個要求都伴隨著每一個請求。
正確驗證一個令牌
伺服器必須按照下列順序檢查:
- The algorithm is one you expect. Configure an allow-list such as
["RS256"]。永遠不要讓代幣的頭部自由決定;歷史漏洞讓攻擊者切換到none或欺騙伺服器使用公鑰作為HMAC秘密。 - 簽名是有效的 用正確的金鑰,由
kid從您所信任的金鑰集中選擇。 exp在未來,nbf在過去,允許小時鐘偏差3060秒。issmatches your identity provider exactly。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索賠捆綁。它的安全性完全來自伺服器上的簽名驗證和索賠驗證。解碼是無害的,並且對除錯有用;信任未經驗證的解碼資料是真正的風險。保持代幣的使用壽命短,保持小的有效負載和沒有秘密,每次驗證演算法,簽名,到期,發行人和受眾,
本頁內容由英文自動翻譯而來,如發現錯誤,歡迎告訴我們。