什麼是令牌?
在介紹不透明令牌之前,了解令牌是什麼很重要:
令牌用於在各方之間表示和傳輸安全信息,支持互聯網上大多數的認證 (authentication) 和授權 (authorization) 過程。在網路服務中,最受歡迎的兩種類型的令牌是 RFC 7519: JSON Web Tokens (JWT) 和不透明令牌。
什麼是不透明令牌?
不透明令牌是採用專有格式的令牌,你無法直接訪問,通常包含某個識別符,用於在伺服器的持久儲存中查找信息。
不透明令牌是令牌能夠得到的一種形式,存取令牌 (access token) 和刷新令牌 (refresh token) 都可以是以不透明令牌的形式存在。不透明令牌的格式是由發行者 (issuer) 決定的,通常是一串數字和/或字符,用於幫助發行者在數據庫中檢索和識別某些信息。以下是一個不透明令牌的例子:
M-oxIny1RfaFbmjMX54L8Pl-KQEPeQvF6awzjWFA3iq
另一方面,JWT 是另一種常見的令牌格式。它是一個包含所有權益 (claims) 和信息的 JSON 字串,並具有發行者 (issuer) 的簽名。默認情況下,它不是加密的,但可以使用 JSON Web Encryption (JWE) 標準加密。即便 JWT 通常未加密,但這並不危害其安全性──簽名的存在確保了令牌內容的完整性,使得 JWT 內的數據可以被完全信任。
與 JWT 不同,直接在受保護的資源上需要驗證的信息都包含在內,不透明令牌並不像 JWT 可以由資源直接驗證。相反,它們需要由不透明令牌的發行者 (通常是授權伺服器) 進行驗證。此驗證過程通常被稱為令牌自省 (token introspection)。
什麼是 JWT?
作為與不透明令牌的對比,JWT 是一個自包含的、無狀態的令牌,以結構化且可讀的格式攜帶信息。
JWT 是由三部分組成的:header
、payload
和 signature
,每個部分使用 Base64URL 編碼。
以下是一個 JWT 的例子:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
header
包含有關令牌類型和用於簽名的算法的信息。例如,{"alg": "HS256", "typ": "JWT"}
。payload
部分包含權益 (claims)──關於用戶或授權的片段信息,例如用戶 ID、過期時間和範圍。因為這些數據是編碼而非加密的,拿到令牌的人可以解碼以查看權益 (claims),但無法在不使簽名無效的情況下進行修改。依賴於規範和授權伺服器配置,payload 中可以包含各種權益 (claims)。這賦予令牌其自包含的屬性。例如,{"sub": "1234567890", "name": "John Doe", "iat": 1516239022}
。signature
是通過將 header、payload 與一個秘鑰結合並使用指定算法生成的簽名。該簽名用於驗證令牌的完整性,確保其未被篡改。
JWTs 常被使用,因為它們能在本地由客戶端或任何服務驗證,無需與授權伺服器互動。這使得 JWTs 在分佈式系統中特別高效,尤其是當多個服務可能需要獨立驗證令牌的真實性時。
然而,這種便利也帶來了責任,即確保令牌的權益 (claims) 不會過度暴露,因為其對任何可以接觸到令牌的人都是可見的。此外,JWTs 通常具有較短的生命周期,過期時間被包含在令牌的權益 (claims) 中,以確保令牌不會無限期有效。
不透明存取令牌驗證
一個不透明存取令牌需要被發送回授權伺服器進行驗證。授權伺服器維護已發令牌的狀態,並能夠根據其內部儲存來決定令牌的有效性。
- 客戶端向授權伺服器請求存取令牌。
- 授權伺服器發出一個不透明令牌。
- 客戶端在標頭中附上不透明令牌發送資源存取請求。
- 資源提供者發送令牌自省 (token introspection)( RFC 7662: OAuth 2.0 Token Introspection )請求到授權伺服器,進行令牌驗證。
- 授權伺服器返回令牌信息。
JWT 存取令牌驗證(離線)
JWT 存取令牌可以由擁有令牌公鑰的客戶端或任何服務離線驗證。
- 資源提供者預先從 OIDC 探索端點獲取授權伺服器的公鑰。此公鑰用於驗證令牌的簽名並確保其完整性。
- 客戶端向授權伺服器請求存取令牌。
- 授權伺服器發出一個 JWT 令牌。
- 客戶端在標頭中發送帶有 JWT 令牌的資源存取請求。
- 資源提供者使用從授權伺服器獲取的公鑰解碼並驗證 JWT 令牌。
- 資源提供者根據令牌的有效性授予存取。
OIDC 的使用案例
在 OIDC (OpenID Connect) 的背景下,不透明令牌和 JWT 在不同場景下有不同的用途。
不透明令牌
- 用戶資料檢索:
默認情況下,當客戶端在沒有指定資源的情況下請求存取令牌並包含 openid
範圍時,授權伺服器會發出不透明存取令牌。此令牌主要用於從 OIDC /oidc/userinfo
端點檢索用戶資料信息。在收到帶有不透明存取令牌的請求時,授權伺服器會檢查其內部儲存以檢索相關的授權信息並驗證令牌的有效性,然後再回應用戶資料細節。
- 刷新令牌交換:
刷新令牌設計為僅在客戶端和授權伺服器之間交換,不需要與資源提供者共享。因此,刷新令牌通常發行為不透明令牌。當當前的存取令牌過期時,客戶端可以使用不透明刷新令牌獲取新的存取令牌,以確保持續存取而不需重新認證使用者。
JWTs
- ID 令牌:
在 OIDC 中,ID 令牌是包含用戶信息的 JWT,用於認證用戶。通常在發出存取令牌時一起發行,ID 令牌允許客戶端驗證用戶的身份。例如:
// ID 令牌的解碼載荷
{
"iss": "<https://logto.io>",
"sub": "1234567890",
"aud": "client_id",
"exp": 1630368000,
"name": "John Doe",
"email": "[email protected]",
"picture": "<https://example.com/johndoe.jpg>"
}
客戶端可以驗證 ID 令牌以確保用戶的身份,並提取用戶信息以便個性化或授權。ID 令牌僅用於一次性使用,不應用於 API 資源授權。
- API 資源存取(使用存取令牌):
當客戶端請求帶有特定資源指示符的存取令牌時,授權伺服器會發出一個適合用於存取該資源的 JWT 存取令牌。JWT 包含資源提供者可以用來授權客戶端存取的權益 (claims)。例如:
// JWT 存取令牌的解碼載荷
{
"iss": "<https://dev.logto.app>",
"sub": "1234567890",
"aud": "<https://api.example.com>",
"scope": "read write",
"exp": 1630368000
}
資源提供者可以通過檢查權益 (claims) 來驗證請求:
iss
: 確認令牌是由受信任的授權伺服器發行的。sub
: 識別與令牌相關的用戶。aud
: 確保令牌旨在針對特定資源。scope
: 驗證授予用戶的許可權。