IdentityServer4登录回调遇UserNotFoundException时,如何代码触发外部IDP登出?
IdentityServer4外部登录回调中处理用户不存在时的外部IDP登出实现
问题场景
我正在修改基于IdentityServer4的代码,遇到这样的需求:在登录流程的回调Action中,当查询不到本地用户(触发UserNotFoundException)时,需要将用户从外部身份提供商(External Identity Provider)登出,之后强制用户重新登录。
尝试过的思路都依赖IdentityServer生成的logoutId,但直接调用CreateLogoutContextAsync()获取到的logoutId总是为null,因为此时并没有主动触发登出流程。
原代码(带待实现标记)
/// <summary> /// Post processing of external authentication /// </summary> [HttpGet] public async Task<IActionResult> Callback() { try { // Read external identity from the temporary cookie AuthenticateResult result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); if (!result.Succeeded)// if external authentication failed, just produce a login here URL, no need to logout from any (internal/external) provider. { if (result.Failure != null) { _logger.Error(result.Failure); } // todo: url for login // login url for should be passed via model return RedirectToAction("AccessDenied", "Account"); } // Lookup our user and external provider info (User user, string provider, string providerUserId, IEnumerable<Claim> claims) = await FindUserFromExternalProviderAsync(result); if (user == null) { // todo: aws or webex, LogoutId here // todo: delete local authentication cookies //var logoutId = await _interaction.CreateLogoutContextAsync();//it always comes as null, probably because the logout process is not initiated //var model = new LoggedOutViewModel //{ // LogoutId = logoutId, // Reason = LogoutReasons.ACCESS_DENIED, //}; // build a model so the logged out page knows what to display //var vm = await BuildLoggedOutViewModelAsync(model.LogoutId, model.Reason); // todo: url for login throw new UserNotFoundException(providerUserId, null, provider);// pass LogoutId url here //// Redirect to the external identity provider for logout //string url = Url.Action("Logout", new { logoutId = vm.LogoutId, reason = vm.Reason }); //// this triggers a redirect to the external provider for sign-out //return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme); } // This allows us to collect any additional claims or properties for the specific protocols used and store them in the local auth cookie // This is typically used to store data needed for sign out from those protocols var additionalLocalClaims = new List<Claim>(); var localSignInProps = new AuthenticationProperties(); ProcessLoginCallbackForOidc(result, additionalLocalClaims, localSignInProps); ProcessLoginCallbackForSaml2p(result, additionalLocalClaims, localSignInProps); // Issue authentication cookie for user var identityServerUser = new IdentityServerUser(user.Subject) { DisplayName = user.Login, IdentityProvider = provider, AdditionalClaims = additionalLocalClaims }; await HttpContext.SignInAsync(identityServerUser, localSignInProps); // Delete temporary cookie used during external authentication await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); // Retrieve return URL string returnUrl = result.Properties.Items["returnUrl"] ?? "~/"; // Check if external login is in the context of an OIDC request AuthorizationRequest context = await _interaction.GetAuthorizationContextAsync(returnUrl); await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.Subject, user.Login, true, context?.Client.ClientId)); if (context != null && context.IsNativeClient()) { // The client is native, so this change in how to // return the response is for better UX for the end user. return this.LoadingPage("Redirect", returnUrl); } return Redirect(returnUrl); } catch (UserNotFoundException ex) { //_logger.Error() here return RedirectToAction("AccessDenied", "Account"); } }
解决方案实现
不需要依赖logoutId,直接利用外部认证会话的现有信息触发外部IDP登出,步骤如下:
- 清理IdentityServer存储外部认证信息的临时Cookie
- 获取外部身份提供商的认证Scheme
- 构造登出后要跳转的登录页URL
- 调用
SignOut方法触发外部IDP的登出流程,并重定向到登录页
修改后的代码片段(替换原user == null分支的逻辑)
if (user == null) { // 1. 清理IdentityServer的外部临时Cookie await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); // 2. 获取外部身份提供商的认证Scheme(从外部认证结果中提取) var externalAuthScheme = result.Principal.Identity.AuthenticationType; // 3. 构造登出后跳转的登录页URL(可携带原请求路径) var postLogoutRedirectUrl = Url.Action("Login", "Account", new { returnUrl = result.Properties.Items["returnUrl"] }); // 4. 触发外部IDP登出,完成后重定向到登录页 return SignOut( new AuthenticationProperties { RedirectUri = postLogoutRedirectUrl }, externalAuthScheme ); }
补充说明
- 外部认证的Scheme可以从
result.Principal.Identity.AuthenticationType直接获取,也可以从result.Properties.Items["scheme"]读取(取决于IdentityServer版本和配置) - 登出后重定向的URL携带原
returnUrl,可让用户重新登录后回到原本的请求路径 - 此方法适用于OIDC、SAML2等主流外部认证协议,ASP.NET Core会自动根据Scheme对应的认证处理器发起对应登出请求(比如OIDC的
end_session_endpoint)
内容的提问来源于stack exchange,提问作者DayTimeCoder
相关产品推荐
相关产品推荐

