商店後台的身分驗證
有的人會說
「我是為你好」
「你可以相信我」
「我一直都是站在你這邊的」
然而,真正重要的是,過去的行為一直在替你背書
就像系統不相信前端傳來的 Supplier ID,人生也不該只相信別人替自己貼的標籤
- 「我是朋友」→ 那你在我低潮時有沒有出現?
- 「我是專業的」→ 有沒有實績、作品、他人驗證?
- 「我是家人」→ 那你有沒有尊重我的界線?
身分,是被驗證出來的,不是宣稱出來的

系統真正可信的登入身份只能來自後端的驗證結果,而不是前端傳來的任何值
使用者登入後,系統已在後端掌握其真實身份,後端(如 Session、Token、Context)必須知道「現在是誰在操作」
例如,Service 層透過 UserService 取得當前登入身份,在業務邏輯層,不直接相信 Controller 或前端傳入的 Supplier ID,而是呼叫 NineYi.Sms.BL.Services.Users.UserService 來取得登入者資訊,因此我們也需要避免前端傳入不必要的身份欄位(如 Supplier ID),如果這個值「理論上一定等於登入者身份」,那就不該讓前端傳,直接由後端補齊
若業務需求必須傳入,例如允許一個帳號可以管理多個商店,這些人可能是集團帳號 / 營運人員,他們可以透過人管理多間商店,此時 API 的設計,會注入 userService,在使用方法前,檢查 ShopId 的合法性,以避免 A 店看 B 店的狀況


OIDC 的本質,是把「你是誰」這件事,標準化地交給專門的身份伺服器來證明,而應用系統只負責相信並使用這個結果
電商後台會走到「獨立身分伺服器」,是因為遲早會遇到商家要共用帳號登入多個後台、子帳號 / 員工帳號,行為像是停權某個人但保留商家、強制全平台改 MFA 規則,這些都不是「單一後台系統」該承擔的複雜度,如果不拆,會造成登入邏輯散落在各服務,並且權限規則寫死在 DB schema、商家組織身分異動造成大量改 code,或因為資安事件導致影響整個後台系統!

身分伺服器與應用伺服器互動的方式

使用者在應用系統點擊「登入」,應用系統本身「不驗證帳密」,它只做一件事:我要知道這個人是誰!
應用系統將使用者導向身份伺服器(IdP),這個導向請求是依照 OIDC 標準格式,同時告訴 IdP:「驗證完請回來找我」
使用者在身份伺服器完成登入驗證,帳密、MFA、公司 AD、Google 帳號,這一步「只發生在身份伺服器」,身份伺服器產生 ID Token 並回傳,ID Token 是一張「我已確認此人身分」的數位證書,內容包含使用者身分資訊(claims)
應用系統驗證 ID Token 的可信度,確認簽章、發行者、有效期限,確定這不是偽造的身分,應用系統建立自己的登入狀態,轉成 ClaimsIdentity
寫入 Cookie / Session,從此視為「已登入」
OAuth2 是什麼?
它是「授權(Authorization)」的協定,也就是讓別人可以幫我做事,但不需要給我密碼。像是在後台授權給身分驗證器,OIDC 在 OAuth2 上加一層「身分驗證(Authentication)」,讓你知道「是誰登入的」。例如加上一個 id_token,裡面是使用者的身分資訊,而 ID Token 是一個「JWT 格式的身分憑證」,證明「這個人是誰」,通常是由 Identity Provider(身份伺服器)簽章發出

✅ Step 1:使用者發起登入(透過 OpenID Connect)
- 使用者從瀏覽器發起登入(登入按鈕 → 被導向到身份伺服器)
- 使用 OIDC 標準發送授權請求(response_type = id_token)
- 完成登入後,身份伺服器(如 IdentityServer)會
- 回傳一個 ID Token
- 瀏覽器導回 RedirectUri

✅ Step 2: Middleware 處理登入流程
1 | app.UseCookieAuthentication(...); |
- 使用者收到的 Token 被處理後 → 轉換成 ClaimsIdentity
SecurityTokenValidated被觸發,這邊是自訂的邏輯
1 | SecurityTokenValidated = n => this.OnSecurityTokenValidated(n, expiration), |

✅ Step 3:後端將登入資訊寫入 Session + Cookie
OnSecurityTokenValidated() 會將從 OIDC 回來的 Token 解析出
- 使用者名稱 (name)
- 操作者 (operator_name)
- 角色 (role)
- session id (sid)
- token (session_token) — 如果有開啟 Session 管理
呼叫內部的 UserService 查詢更多的使用者資訊,並建立一個新的 ClaimsIdentity
- 加入使用者資料 (UserData)
- 加入角色、操作人、sid 等資訊
接著使用 OWIN 提供的 AuthenticationTicket 將 Identity 寫入
1 | notification.AuthenticationTicket = new AuthenticationTicket(nid, ...); |
同時寫入 StateMachine,把 Claims + UserData + Token 存入 Redis / Cache(例如 ICacheProvider)
並且同時寫入 Cookie,app.UseCookieAuthentication() 設定了 Cookie,Cookie 中帶有 Ticket,未來每次請求都會驗證這個 Cookie

✅ Step 4:自訂驗證機制檢查登入狀態
有自訂一個檢查登入的 Middleware
1 | app.Use<CustomAuthenticationMiddleware>(); |
後續請求於每次請求進來時
- 從 Cookie 中解析出 sid
- 從 Redis / Cache 查找是否有對應 session
- 放到 HttpContext.Items[“IsLogin”] = true 作為後續登入狀態的依據
然後再加上一層檢查身份是否有效
1 | OnValidateIdentity = async context => |
這就是所謂的「驗證使用者目前的 Session 是否還有效」,防止 Cookie 有效但 Redis 中 session 被清除的狀況

| 階段 | 驗證方式 |
|---|---|
| 登入當下 | 使用 OpenID Connect 身份伺服器驗證 |
| 回傳之後 | 驗證 Token,轉換成 ClaimsIdentity |
| 寫入 Cookie | 使用 CookieAuthenticationOptions 管理 |
| 後續請求 | Cookie 自動帶上,透過自訂中介層讀取 sid 驗證 session 狀態 |
| Redis 驗證 | 透過 ICacheProvider 查找 sid 是否仍在 cache 中 |

| 技術 | 說明 |
|---|---|
| Cookie | 儲存 AuthenticationTicket,讓使用者下次請求不用重新登入 |
| Redis(Cache) | 存 sid + UserData + Token → 控制是否還在登入狀態 |
| ClaimsIdentity | 實際代表登入使用者的資料物件,放在 HttpContext.User 裡面 |






