IdentityServer4作为外部提供者,如何避免注销提示?

问题描述 投票:0回答:1

我正在与两个身份提供者合作,它们都使用ASP.NET MVC Core 2.2中的IdentityServer4实现。其中一个被另一个用作外部提供程序。我们称它们为“主要”和“外部”。 Web应用程序直接引用主要提供者。外部提供程序是主要提供程序提供的可选登录方法。

Web应用程序使用oidc-client-js库来实现身份验证。 Web应用程序中的注销操作将调用UserManager.signoutRedirect。使用主要身份提供者时,此方法工作正常(不显示注销确认提示)。但是,当使用外部提供程序时,系统会提示用户从外部提供程序注销。

注销时的请求顺序是:

  • 获取http:// {primary} /connect/endsession?id_token_hint=...&post_logout_redirect_uri=http:// {webapp}
  • 获取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
  • 获取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参数将“认证”注销请求,这使我们可以基于ShowSignoutPrompt返回的GetLogoutContextAsync属性绕过提示。但是,将用户重定向到外部提供程序时不会发生这种情况。对SignOut的调用将生成带有state参数但不包含id_token_hint的第二个/ connect / endsession URL。

外部提供程序中的注销代码基本上与上面显示的代码相同。当它调用GetLogoutContextAsync时,该方法不会将请求视为已验证,因此ShowSignoutPrompt属性为true。

知道如何验证对外部提供者的请求吗?

我正在与两个身份提供者合作,它们都使用ASP.NET MVC Core 2.2中的IdentityServer4实现。其中一个被另一个用作外部提供程序。我们称它们为“主要”和“ ...

c# asp.net-core-mvc identityserver4 oidc
1个回答
0
投票

您讨厌,但幸运的是,最后的代码块包含一个重要的行:

opts.SaveTokens = true;
© www.soinside.com 2019 - 2024. All rights reserved.