我正在与两个身份提供者合作,它们都使用ASP.NET MVC Core 2.2中的IdentityServer4实现。其中一个被另一个用作外部提供程序。我们称它们为“主要”和“外部”。 Web应用程序直接引用主要提供者。外部提供程序是主要提供程序提供的可选登录方法。
Web应用程序使用oidc-client-js库来实现身份验证。 Web应用程序中的注销操作将调用UserManager.signoutRedirect
。使用主要身份提供者时,此方法工作正常(不显示注销确认提示)。但是,当使用外部提供程序时,系统会提示用户从外部提供程序注销。
注销时的请求顺序是:
上面的最后一个请求显示了来自外部提供商的注销确认屏幕。
主要提供者上/ Account / Logout页面的代码与sample code in the documentation几乎相同:
[HttpGet]
public async Task<IActionResult> Logout(string logoutId)
{
var vm = await BuildLogoutViewModelAsync(logoutId);
if (!vm.ShowLogoutPrompt)
{
// If the request is authenticated don't show the prompt,
// just log the user out by calling the POST handler directly.
return Logout(vm);
}
return View(vm);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout(LogoutInputModel model)
{
var vm = await BuildLoggedOutViewModelAsync(model.LogoutId);
if (User?.Identity.IsAuthenticated)
{
// delete local authentication cookie
await _signInManager.SignOutAsync();
// raise the logout event
await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
}
// check if we need to trigger sign-out at an upstream identity provider
if (vm.TriggerExternalSignout)
{
// build a return URL so the upstream provider will redirect back
// to us after the user has logged out. this allows us to then
// complete our single sign-out processing.
var url = Url.Action("Logout", new { logoutId = vm.LogoutId });
// this triggers a redirect to the external provider for sign-out
var ap = new AuthenticationProperties { RedirectUri = url };
return SignOut(ap, vm.ExternalAuthenticationScheme);
}
return View("LoggedOut", vm);
}
BuildLogoutViewModelAsync
方法调用GetLogoutContextAsync
以检查注销是否已通过身份验证,例如:
public async Task<LogoutViewModel> BuildLogoutViewModelAsync(string logoutId)
{
var vm = new LogoutViewModel
{
LogoutId = logoutId,
ShowLogoutPrompt = true
};
var context = await _interaction.GetLogoutContextAsync(logoutId);
if (context?.ShowSignoutPrompt == false)
{
// It's safe to automatically sign-out
vm.ShowLogoutPrompt = false;
}
return vm;
}
BuildLoggedOutViewModelAsync
方法基本上只检查外部身份提供者,并设置TriggerExternalSignout
属性(如果使用了该属性。)>
我不喜欢将其作为代码墙,但是我将包括用于配置主身份服务器的ConfigureServices
代码,因为它可能是相关的:
var authenticationBuilder = services.AddAuthentication(); authenticationBuilder.AddOpenIdConnect(openIdConfig.Scheme, "external", ConfigureOptions); void ConfigureOptions(OpenIdConnectOptions opts) { opts.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; opts.SignOutScheme = IdentityServerConstants.SignoutScheme; opts.Authority = openIdConfig.ProviderAuthority; opts.ClientId = openIdConfig.ClientId; opts.ClientSecret = openIdConfig.ClientSecret; opts.ResponseType = "code id_token"; opts.RequireHttpsMetadata = false; opts.CallbackPath = $"/signin-{openIdConfig.Scheme}"; opts.SignedOutCallbackPath = $"/signout-callback-{openIdConfig.Scheme}"; opts.RemoteSignOutPath = $"/signout-{openIdConfig.Scheme}"; opts.Scope.Clear(); opts.Scope.Add("openid"); opts.Scope.Add("profile"); opts.Scope.Add("email"); opts.Scope.Add("phone"); opts.Scope.Add("roles"); opts.SaveTokens = true; opts.GetClaimsFromUserInfoEndpoint = true; var mapAdditionalClaims = new[] { JwtClaimTypes.Role, ... }; foreach (string additionalClaim in mapAdditionalClaims) { opts.ClaimActions.MapJsonKey(additionalClaim, additionalClaim); } opts.TokenValidationParameters = new TokenValidationParameters { NameClaimType = JwtClaimTypes.Name, RoleClaimType = JwtClaimTypes.Role }; }
[我的理解是,传递给第一个/ connect / endsession端点的
id_token_hint
参数将“认证”注销请求,这使我们可以基于ShowSignoutPrompt
返回的GetLogoutContextAsync
属性绕过提示。但是,将用户重定向到外部提供程序时不会发生这种情况。对SignOut
的调用将生成带有state
参数但不包含id_token_hint
的第二个/ connect / endsession URL。外部提供程序中的注销代码基本上与上面显示的代码相同。当它调用
GetLogoutContextAsync
时,该方法不会将请求视为已验证,因此ShowSignoutPrompt
属性为true。
知道如何验证对外部提供者的请求吗?
我正在与两个身份提供者合作,它们都使用ASP.NET MVC Core 2.2中的IdentityServer4实现。其中一个被另一个用作外部提供程序。我们称它们为“主要”和“ ...
您讨厌,但幸运的是,最后的代码块包含一个重要的行:
opts.SaveTokens = true;