Token 驗證契約
Gateway 對每個請求做兩段檢查。這一頁說明你發出的 token 會被怎麼驗,以便你在送出前就排除可預期的 401。
第一段:本地驗簽(沒有網路依賴)
| 檢查 | 規則 |
|---|---|
| 演算法 | ES256 |
| 金鑰來源 | 固定 JWKS https://auth.lightup.tech/.well-known/jwks.json |
| issuer | 只接受平台 issuer;絕不依 token 自報的 iss 去別處取 key |
| audience | aud == lightup-api,單值。不是 client id |
| 用途 | JOSE header typ == at+jwt;缺或不同就拒 |
| profile | 必須是 self 或 workload;其他值 v1 不開放給第三方(這一關由授權端拒,見下) |
| 時間 | exp 未過;iat 以 min(iat, now) 計算,未來的 iat 不會延長壽命 |
aud = lightup-api 是刻意的:Gateway 是唯一的資源伺服器。所以「A 應用的 token 拿去打 B 應用的資源」不會因為 audience 不同而被擋——擋它的是第二段的授權檢查,不是 audience。
unknown kid:JWKS 輪替時平台會有界重取並限流。重取失敗回 503,不是 401——因為那是「現在驗不了」,不是「這張 token 無效」。
第二段:每個請求問一次授權
Gateway 對每個請求向身分服務查當前授權,檢查:
- client 存在、啟用中,且確實是第三方 client;
- service account(
workload)或使用者 session(self)未被撤銷; - token 帶的 actor epoch 等於現值;
- 租戶對這支應用的 grant 仍含這項能力;
- 租戶方案仍含這項能力;
- 資源政策成立(例如
person型資源:這個租戶對這個人有有效關係)。
v1 沒有 allow-cache。 每個請求問一次,換來的是可以誠實承諾的撤權語意:撤權在下一個請求生效。
fail-closed:判不了就不放行,回 503 authorization_unavailable(可重試),絕不降級成「只驗簽就放行」。
你這端該驗什麼
如果你的後端自己也要看 token(例如記 log 或做路由),請注意:
- 不要用未驗簽的方式解 token 取
tenant_id或sub來做授權判斷。要用就完整驗簽。 - JWKS 可以自己快取(這是公開金鑰,快取沒有安全問題),但要處理 unknown kid 的有界重取,並且限流。
- 不要因為本地驗簽通過就假設平台會放行——授權是平台的第二段檢查,會拒。
- 不要把 token 寫進 log、錯誤回報或分析事件。
401 一律是 invalid_token,不細分
沒帶 token、簽章錯、aud 錯、typ 錯、過期——對外都是同一個答案
invalid_token。這是刻意的:細分等於幫呼叫端(包括攻擊者)確認哪一項才是錯的。
真正的原因記在伺服器端,用 x-request-id 查。
| 症狀 | 該看哪裡 |
|---|---|
| 401,而 token 是登入拿到的 | 你拿了 ID token。ID token 不是 access token |
| 401,而 token 剛換到 | aud 不是 lightup-api,或用了非本平台簽發的 token |
| 間歇 401 | 多台機器共用 token 快取但時鐘不同步;或 refresh 被重放導致 family 被殺 |
| 403 而不是 401,換 token 也一樣 | token 是好的,是授權不准:看 error 代碼(client_disabled、subject_revoked、epoch_changed…),見錯誤・限流・重試 |
delegated 與 shared profile 不會在這裡被擋。 它們的簽章是對的,會走完驗證,
再由授權端回 403 profile_not_supported。換句話說:401 是「這張 token 不能用」,
403 是「這張 token 能用,但不准做這件事」。