Keycloak、OAuth 2.0、OIDC、JWT,常常會一起出現在登入設計裡。我想先把它們一個一個分清楚,再沿著登入流程看一次,這樣比較知道每一步到底驗證了什麼。
有一個問題,我會先提醒自己:如果只看到 Token 有效就放行,就可能漏掉這個人到底能不能查這筆資料。所以使用者能登入,接下來能做哪些事,還要另外確認。測試時也要換不同權限的帳號,才看得到這些差異。
Payload 的內容可以被讀出來,不表示它值得信任,所以 JWT 也不能只看能不能解碼;簽章與 claims 都驗證完成,才有依據使用裡面的資訊。
入口與服務兩邊都驗一次,初看會覺得有點重複。不過,Gateway 可以先擋下無效請求,服務則要確認到自己這裡的請求仍符合要求。即使有人繞過入口,服務也保有自己的檢查。
所以,我會先讓 Gateway 檢查 Token 簽章、issuer 與有效期。請求到了業務服務,再由 Resource Server 獨立驗證同一個 JWT,接著判斷資源權限。每一層把自己的工作做好,才不會只靠「已經經過入口」這個假設。

圖 Day 11-1:Keycloak OIDC 登入流程。
下游需要依原始呼叫者的身分判斷,知道目前是誰在操作。所以這個系列採用的方式,是讓 Gateway 轉發原本的 Authorization header,不在入口換成另一個服務帳號。
前端這一段,採用 Authorization Code Flow with PKCE;後端設定 Resource Server,服務帳號再依情境使用 client credentials。前端不使用 Implicit Flow,也不保存長期憑證,這些基本條件要一起確認。
Realm、client 與 role 的設定要能追蹤異動,secret 則放在有權限控管、有異動紀錄的地方,不進 repository 也不進 Log。因為網址可能一路留在瀏覽器歷程、proxy 與存取紀錄中,所以 Token 也不要放進 URL 或 Log。這些地方都值得在測試時多看一次。
issuer 也要逐字確認,包含主機名稱。如果服務連的是容器內部名稱,Token 寫的卻是對外網址,就需要回頭核對設定。遇到 401,再從有權限管控的 Log 查明實際的驗證原因。
| 取捨 | 這樣選的理由 | 何時要重新評估 |
|---|---|---|
| 入口與服務各驗一次 JWT | 內網不作為信任邊界 | 驗證成本成為效能瓶頸時,需先量測再調整 |
| 前端採 Authorization Code + PKCE | 不需在前端保存 client secret | 標準演進或裝置條件改變時 |
| Gateway 原封不動轉發 Authorization header | 下游需要原始呼叫者身分做授權 | 有必須隱藏原始身分的整合情境時 |
| 身分管理集中於單一 IdP | 帳號與角色治理才可能一致 | IdP 可用性影響範圍超出可接受程度時 |
身分管理集中後,IdP 的可用性會影響登入與 Token 取得。既有的有效 Token 在到期前仍可使用,因此影響主要落在新登入與續期;規劃 Keycloak 要開幾台、Token 有效期設多長、掛掉時怎麼處理,都要照這個特性來,不必當成「Keycloak 一掛,全部服務馬上停」。還有一條要寫進去:Gateway 是把 Keycloak 的公鑰抓回來快取後自己驗簽章,所以 Keycloak 掛掉期間不要重啟 Gateway,否則抓不到公鑰,所有 Token 都驗不過。
除了正常登入,我也會多花一點時間測試不能通過的情況:
| 測試情境 | 預期行為 | 想確認什麼 |
|---|---|---|
| Token 過期 | 401,且不因快取而放行 | 有效期檢查是否真的生效? |
| issuer 錯誤 | 401 | 是否只驗簽章而未驗發行者? |
| 簽章變更或金鑰輪替 | 401 或依新金鑰驗證成功 | 金鑰輪替是否會造成中斷? |
| 登出後使用舊 Token | 依設計拒絕或於到期後失效 | 登出語意是否與實作一致? |
| 時鐘偏差 | 在容許範圍內仍可驗證 | 容許值是否明確設定?本系列目前沿用 Spring Security 預設值 |
| IdP 無法使用 | 既有 Token 可用,新登入失敗且錯誤明確 | 降級行為是否可預期? |
JWT 採無狀態驗證時,登出未必會讓已發出的 Token 立刻失效。登出後舊 Token 還能不能用,這件事要先講清楚。把預期行為寫好、測過,使用者和團隊才會有一致的理解。
今天先把登入流程中的角色分開來看。接下來,我想繼續整理登入之後的事情:這個人可以做什麼,又能看到哪些資料?下一篇,就從角色、資源與資料範圍開始。