【发布时间】:2020-08-07 14:46:45
【问题描述】:
我正在使用两个身份提供者,它们都使用 ASP.NET MVC Core 2.2 中的 IdentityServer4 实现。其中一个被另一个用作外部提供者。我们称它们为“主要的”和“外部的”。主要提供者由 Web 应用程序直接引用。外部提供者是主提供者提供的可选登录方式。
Web 应用程序使用oidc-client-js 库来实现身份验证。 Web 应用程序中的注销操作调用UserManager.signoutRedirect。这在使用主要身份提供者时工作正常(不显示注销确认提示)。但是,当使用外部提供程序时,系统会提示用户退出外部提供程序。
注销时的请求顺序为:
- GET http://{primary}/connect/endsession?id_token_hint=...&post_logout_redirect_uri=http://{webapp}
- GET http://{primary}/Account/Logout?logoutId=...
- GET http://{external}/connect/endsession?state=...&post_logout_redirect_uri=http://{primary}/signout-callback-{idp}&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver= 5.3.0.0
- GET http://{external}/Account/Logout?logoutId=...
上面的最后一个请求显示了来自外部提供商的注销确认屏幕。
主要提供商上 /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 参数将“验证”注销请求,这允许我们根据GetLogoutContextAsync 返回的ShowSignoutPrompt 属性绕过提示。但是,当用户被重定向到外部提供者时,这不会发生。对SignOut 的调用会生成第二个带有state 参数的/connect/endsession URL,但没有id_token_hint。
external provider中的注销代码与上面显示的代码基本相同。当它调用GetLogoutContextAsync 时,该方法不会将请求视为经过身份验证,因此ShowSignoutPrompt 属性为true。
知道如何验证对外部提供者的请求吗?
【问题讨论】:
标签: c# asp.net-core-mvc identityserver4 openid-connect