C# IdentityServer4作为外部提供程序,如何避免注销提示?
我正在使用两个身份提供者,它们都是在ASP.NET MVC Core 2.2中使用IdentityServer 4实现的。其中一个被另一个用作外部提供者。让我们称之为“主要”和“外部”。web应用程序直接引用主提供程序。外部提供程序是主提供程序提供的可选登录方法 web应用程序使用库来实现身份验证。web应用程序中的注销操作调用C# IdentityServer4作为外部提供程序,如何避免注销提示?,c#,asp.net-core-mvc,identityserver4,openid-connect,C#,Asp.net Core Mvc,Identityserver4,Openid Connect,我正在使用两个身份提供者,它们都是在ASP.NET MVC Core 2.2中使用IdentityServer 4实现的。其中一个被另一个用作外部提供者。让我们称之为“主要”和“外部”。web应用程序直接引用主提供程序。外部提供程序是主提供程序提供的可选登录方法 web应用程序使用库来实现身份验证。web应用程序中的注销操作调用UserManager.signoutRedirect。当使用主标识提供程序时(不显示注销确认提示),此功能可以正常工作。但是,当使用外部提供程序时,会提示用户从外部提供
UserManager.signoutRedirect
。当使用主标识提供程序时(不显示注销确认提示),此功能可以正常工作。但是,当使用外部提供程序时,会提示用户从外部提供程序注销
注销时的请求顺序为:
- 获取http://{primary}/connect/endsession?id_-token_-hint=…&post_-logout_-redirect_-uri=http://{webapp}
- 获取http://{primary}/Account/Logout?logoutId=
- 获取http://{external}/connect/endsession?state=…&post_logout_redirect_uri=http://{primary}/signout callback-{idp}&x-client-SKU=ID\u NETSTANDARD2\u 0&x-client-ver=5.3.0.0
- 获取http://{external}/Account/Logout?logoutId=
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\u token\u hint
参数将“验证”注销请求,这允许我们根据GetLogoutContextAsync
返回的ShowSignoutPrompt
属性绕过提示。但是,当用户重定向到外部提供程序时,不会发生这种情况。调用SignOut
生成第二个/connect/endsession URL,其中包含state
参数,但没有id\u token\u hint
外部提供程序中的注销代码与上面显示的代码基本相同。当调用GetLogoutContextAsync
时,该方法不会将请求视为已验证,因此ShowSignoutPrompt
属性为true
您知道如何向外部提供者验证请求吗?您不喜欢但幸运地添加的最后一段代码包含一行重要内容:
opts.SaveTokens=true;
这允许您稍后还原从外部提供程序获得的id\u令牌。
然后您可以将其用作“第二级提示”
if(vm.TriggerExternalSignout)
{
var url=url.Action(“注销”,新的{logoutId=vm.logoutId});
var props=newauthenticationproperties{RedirectUri=url};
SetParameter(“id_token_hint”,HttpContext.GetTokenAsync(“id_token”);
返回注销(props、vm.ExternalAuthenticationScheme);
}
我提出了一个解决方案,尽管它似乎与示例中所做的相矛盾
这个问题似乎是由两行代码引起的,这两行代码都来自IdentityServer示例,我们将其用作IDP实现的基础。问题代码位于“主要”IDP中
第一行位于Startup.cs中的ConfigureServices
:
var authenticationBuilder = services.AddAuthentication();
authenticationBuilder.AddOpenIdConnect(openIdConfig.Scheme, "external", ConfigureOptions);
void ConfigureOptions(OpenIdConnectOptions opts)
{
opts.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
opts.SignOutScheme = IdentityServerConstants.SignoutScheme; // this is a problem
第二个位置是ExternalController.cs中的回调
方法。这里我们使用IdentityServerConstants.ExternalCookieAuthenticationScheme
而不是IdentityConstants.ExternalScheme
,与示例不同:
// Read external identity from the temporary cookie
var result = await this.HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
// ...
// delete temporary cookie used during external authentication
await HttpContext.SignOutAsync(
IdentityServerConstants.ExternalCookieAuthenticationScheme); // this is a problem
注销时发生的情况是:SignOutScheme
被覆盖,它正在寻找一个不存在的cookie。简单地删除它并不能修复它,因为对SignOutAsync
的调用删除了包含身份码验证方案所需信息的cookie。由于它无法对方案进行身份验证,因此在对“外部”IDP的请求中不包括id\u token\u hint
通过删除Startup.cs中覆盖SignOutScheme
的代码,并将删除ExternalCookieAuthenticationScheme
cookie的代码移动到AccountController.cs中的Logout
端点,我已经解决了这个问题:
// check if we need to trigger sign-out at an upstream identity provider
if (vm.TriggerExternalSignout)
{
// delete temporary cookie used during external authentication
await this.HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
// build a return URL so the upstream provider will redirect back...
通过这种方式,“临时”外部cookie会一直保留到需要时,但会在用户注销时删除
我不确定这是否是“正确”的解决方案,但它似乎在我测试过的所有情况下都能正常工作。我也不确定为什么我们会偏离ExternalController.cs中的示例,但我怀疑这是因为我们有两个独立的IDP,而不是一个站点有一个独立的IDP。此外,当我们使用混合流时,该示例似乎使用隐式流。我遇到了与OP完全相同的问题,并且能够通过明确声明ID令牌将根据此Github问题添加到注销请求中来纠正它
我已经试过了,我能找回令牌。不幸的是,SetParameter
调用没有导致id\u token\u提示
包含在外部提供者的初始请求中。我还尝试使用StoreTokens
将其添加到身份验证属性中,但这也不起作用。当您为注销调用传递一个全新的AuthenticationProperties
实例时,您不需要再存储任何内容,您只需将参数添加到您的注销请求中,SetParameter
方法正是针对该作业的!杰卡,这里有进展吗?在我的tes中
// Read external identity from the temporary cookie
var result = await this.HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
// ...
// delete temporary cookie used during external authentication
await HttpContext.SignOutAsync(
IdentityServerConstants.ExternalCookieAuthenticationScheme); // this is a problem
// check if we need to trigger sign-out at an upstream identity provider
if (vm.TriggerExternalSignout)
{
// delete temporary cookie used during external authentication
await this.HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
// build a return URL so the upstream provider will redirect back...
options.SaveTokens = true; // required for single sign out
options.Events = new OpenIdConnectEvents // required for single sign out
{
OnRedirectToIdentityProviderForSignOut = async (context) => context.ProtocolMessage.IdTokenHint = await context.HttpContext.GetTokenAsync("id_token")
};