Sign-out 登出
IdentityServer的登出就像刪除認證cookie一樣簡單,但是為了完成一個完整的聯合簽名,我們必須考慮將用戶從客戶端應用程序中(甚至可能是上游的Identity提供者)登出。
Removing the authentication cookie刪除認證cookie
簡單的調用HttpContext的SignOutAsync方法就能刪除認證cookie,要使用該方法,你需要傳遞那個使用的認證方案(scheme,默認情況下是IdentityServerConstants.DefaultCookieAuthenticationScheme,除非你更改過它):
await HttpContext.SignOutAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme);
或者你也可以使用IdentityServer提供的這個更為便利的擴展方法:
await HttpContext.SignOutAsync();
通常情況下,應該提示用戶登出(意味着需要一個POST),否則攻擊者可以將其鏈接到您的注銷頁面,從而導致用戶自動注銷。
Notifying clients that the user has signed-out通知客戶端用戶已經登出
作為signout這個整體動作的一部分,應該確保客戶端應用程序也得到了用戶登出的信息,對於有服務端的客戶端,IdentityServer提供了對front-channel規范的支持;對於基於瀏覽器的javascript客戶端(例如SPA、React、Angular等),IdentitySever提供了對 session management 規范的支持。
實際上,OIDC定義了三個規范來完成撤銷認證這個動作:
- Session Management :可選。Session管理,用於規范OIDC服務如何管理Session信息。
- Front-Channel Logout:可選。基於前端的注銷機制。
- Back-Channel Logout:可選。基於后端的注銷機制。
其中Session Management是OIDC服務自身管理會話的機制;Back-Channel Logout則是定義在純后端服務之間的一種注銷機制,應用場景不多,這里也不詳細解釋了。這里重點關注一下Front-Channel Logout這個規范(http://openid.net/specs/openid-connect-frontchannel-1_0.html),它的使用最為廣泛,其工作的具體的流程如下(結合Session Management規范):
(上圖來自:https://www.cnblogs.com/linianhui/p/openid-connect-extension.html)
在上圖中的2和3屬於session management這個規范的一部。其中第2步中,odic的退出登錄的地址是通過Discovery服務中返回的end_session_endpoint字段提供的RP的。其中還有一個check_session_iframe字段則是供純前端的js應用來檢查oidc的登錄狀態用的。
4567這四步則是屬於front-channel logout規范的一部分,OIDC服務的支持情況在Discovery服務中也有對應的字段描述:
4567這一部分中重點有兩個信息:
- RP退出登錄的URL地址(這個在RP注冊的時候會提供給OIDC服務);
- URL中的sessionid這個參數,這個參數一般是會包含在idtoken中給到OIDC客戶端,或者在認證完成的時候以一個獨立的sessionid的參數給到OIDC客戶端,通常來講都是會直接把它包含在IDToken中以防止被篡改。
Front-channel server-side clients
在front-channel規范中,為了從帶有服務端的client上登出用戶,identityserver上面的登出頁面必須渲染一個<iframe>來通知client客戶已經登出。希望被通知的客戶端必須設置了FrontChannelLogoutUri
這個配置。IdentityServer跟蹤用戶登入的那個客戶端,並且在IIdentityServerInteractionService
(查看詳情)上面提供了一個GetLogoutContextAsync
的API,這個API返回一個LogoutRequest對象,它帶有一個SignOutIFrameUrl屬性,你的登出頁面必須呈現為<iframe>(原文是:This API returns a LogoutRequest
object with a SignOutIFrameUrl
property that your logged out page must render into an <iframe>
.我這個翻譯很拗口,不知道翻譯的對不對,請指正)
Back-channel server-side clients
要通過back-channel規范從服務器端客戶端應用程序中簽出用戶,identityserver中的SignOutIFrameUrl端點將自動觸發服務器到服務器的調用,將簽名的簽出請求傳遞給客戶端。這意味着,即使沒front-channel客戶端,身份服務器中的“注銷”頁面仍然必須呈現如上所述的SignOutIFrameUrl。希望被通知的客戶端必須有BackChannelLogoutUri配置值集。
Browser-based JavaScript clients
考慮到 session management 規范的設計方式,在identityserver中沒有什么特別的東西,需要做的是通知這些客戶已經登出。但是,客戶端必須在check_session_iframe上執行監控,這是由oidc-client JavaScript library.實現的。
Sign-out initiated by a client application客戶端應用發起的登出請求
如果一個登出請求是被客戶端應用發起的,那么客戶端首先會把用戶重定向到end session endpoint。在處理從end session endpoint通過重定向到登出頁面這件事可能需要保持一些臨時的狀態(state)(比如客戶端登出的重定向uri)。這個狀態或許對於登出頁面是有用的,並且state的標志符( the identifier for the state)也通過一個logoutid的參數傳遞給了logout頁面。
interaction service 上面的GetLogoutContextAsync
API可以用來加載這個state。返回的LogoutRequest對象上的ShowSignoutPrompt屬性指示簽出的請求是否已認證過,並且因此它不提示用戶簽出是安全的。
默認情況下這個state是通過logoutid的值作為一個被保護的數據結構來管理的,通過實現IMessageStore<LogoutMessage>並將其注冊到DI,可以在end session endpoint和登出頁面之間對這個值做一些持久化的工作。