Identity Server 3 + ASP.NET Core 2.0 MVC 应用程序 - 联合单点注销不包括在结束会话前重定向到 ADFS

Identity Server 3 + ASP.NET Core 2.0 MVC app - Federated single sign-out not including a redirect to ADFS before ending session

我的网络应用程序是 Identity Server 3 STS 的客户端,它与外部 IdP 的 ADFS 联合。登录效果很好。从 STS 注销没问题。但是在结束 IdSrv3 会话并最终重定向到应用程序之前,我一直无法让 IdSrv3 重定向到 ADFS 以进行注销。

如果我理解正确的话,我应该能够在退出后让 ADFS post 回到 RP (IdSrv3),此时 IdSrv3

阅读文档: https://identityserver.github.io/Documentation/docsv2/advanced/federated-post-logout-redirect.html

以及围绕这个联合单点注销主题的 GitHub 问题选集。

通过 IdSrv3 进行跟踪,我从未看到尝试重定向到 ADFS 以进行注销,所以我假设我在这里缺少配置。

复杂的是,我是 运行 IdSrv3,但我的客户端应用程序是 ASP.NET Core 2.0,因此许多示例无法与最新的 Microsoft 身份客户端中间件完全协调。

在 IdSrv3 上,这些是(我相信)相关的配置组件:

额外身份提供商的配置:

        var wsFed = new WsFederationAuthenticationOptions
        {
            Wtrealm = ConfigurationManager.AppSettings["Wtrealm"],
            MetadataAddress = metaDataAddress,
            AuthenticationType = "ADFS",
            Caption = "ACME ADFS",
            SignInAsAuthenticationType = signInAsType
        };

IdSrv3 中间件:

coreApp.UseIdentityServer(
                    new IdentityServerOptions
                    {

                        SiteName = "eFactoryPro Identity Server",
                        SigningCertificate = Cert.Load(),
                        Factory = factory,
                        RequireSsl = true,

                        AuthenticationOptions = new AuthenticationOptions
                        {
                            IdentityProviders = ConfigureAdditionalIdentityProviders,
                            EnablePostSignOutAutoRedirect = true,
                            EnableSignOutPrompt = false,
                            EnableAutoCallbackForFederatedSignout = true
                        },
                        LoggingOptions = new LoggingOptions
                        {
                            EnableHttpLogging = true,
                            EnableKatanaLogging = true,
                            //EnableWebApiDiagnostics = true,
                            //WebApiDiagnosticsIsVerbose = true
                        }
                    });
                coreApp.Map("/signoutcallback", cleanup =>
                {
                    cleanup.Run(async ctx =>
                    {
                        var state = ctx.Request.Cookies["state"];
                        await ctx.Environment.RenderLoggedOutViewAsync(state);
                    });
                });
            });

现在对于客户端,ASP.NET Core 2.0 MVC 应用程序:

更新:查看已接受的答案 - 重定向到 IdP 以进行注销应该在 IdSrv3 端就重定向到 外部 IdP 进行处理(ADFS)

       public static void ConfigureAuth(this IServiceCollection services,
      ITicketStore distributedStore,
      Options.AuthenticationOptions authOptions)
    {

        services.AddDataProtection();

        services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
                options.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            }).AddCookie(options =>
            {
                options.ExpireTimeSpan = TimeSpan.FromHours(8);
                options.SlidingExpiration = true;
                options.SessionStore = distributedStore;
            })
            .AddOpenIdConnect(options =>
            {
                options.Authority = authOptions.Authority;
                options.ClientId = authOptions.ClientId;
                options.ClientSecret = authOptions.ClientSecret;

                options.ResponseType = "code id_token";

                options.Scope.Add("openid");
                options.Scope.Add("profile");
                options.Scope.Add("roles");
                options.Scope.Add("email");
                options.Scope.Add("offline_access");

                options.RequireHttpsMetadata = false;

                options.GetClaimsFromUserInfoEndpoint = true;

                options.SaveTokens = true;

                options.Events = new OpenIdConnectEvents()
                {
                    OnRedirectToIdentityProviderForSignOut = n =>
                    {

                        var idTokenHint = n.ProtocolMessage.IdTokenHint;

                        if (!string.IsNullOrEmpty(idTokenHint))
                        {
                            var sessionId = n.HttpContext?.Session?.Id;
                            var signOutRedirectUrl = n.ProtocolMessage.BuildRedirectUrl();

                            if (sessionId != null)
                            {                                    
                                n.HttpContext.Response.Cookies.Append("state", sessionId);
                            }

                            n.HttpContext?.Session?.Clear();

                            n.Response.Redirect(signOutRedirectUrl);
                        }

                        return Task.FromResult(0);
                    }
                };
            });

    }

根据文档,我应该将 "sign out message id" 传递到 'state' cookie 中。但是,此扩展方法在 ASP.NET Core 2.0 中不起作用,因为我们实际上无法再访问 OwinContext。

var signOutMessageId = n.OwinContext.Environment.GetSignOutMessageId();

我什至尝试实例化一个新的 OwinContext(n.HttpContext) 来获取环境字典 - 然而,"GetSignOutMessageId()" 获得的值有一个键 "id"我在 Owin 变量中找不到。

似乎这个 cookie 确实是通过所有重定向保持状态所必需的,以便在我的客户端应用程序的 PostLogoutUri 被命中后,它当前设置为“https://myapp/signout-callback-oidc”,消息 ID 可以用于完成清理会话。

我也对 "EnableAutoCallbackForFederatedSignout = true" 设置在 IdSrv3 配置中扮演的角色感到困惑。

从这个描述和代码中可以看出,这只是让我不必在 ADFS 注销上设置 "WReply" 参数: https://github.com/IdentityServer/IdentityServer3/issues/2613 我希望 ADFS 会重定向到: 如果此设置为 'true'.

,则会自动显示“https://myIdSrv3/core/signoutcallback

如果有人有任何指导分享,我们将不胜感激。

事实证明,我将 IdSrv3 中的一些概念混为一谈,这些概念描述了由外部 Idp 发起的联合单一注销,而不是我的用例 - 由 IdSrv3 客户端应用程序发起的注销,级联 "up" 到外部 IdP。

此问题的根本原因在于我的 UserService 实现。在那里我覆盖了 "AuthenticateExternalAsync()" 方法,但没有在 AuthenticateResult 对象中指定 外部身份提供者

这里是更正后的实现:

        public override Task AuthenticateExternalAsync(ExternalAuthenticationContext context)
        {

         ...

                context.AuthenticateResult = new AuthenticateResult(
                    user.Id, 
                    user.UserName, 
                    new List<Claim>(), 
                    context.ExternalIdentity.Provider);

            return Task.FromResult(0);
        }

一旦在我的 AuthenticateResult 中指定了外部 Idp,我就能够处理 WsFederationAuthenticationNotifications.RedirectToIdentityProvider 事件。

为了完整起见,这是我的代码,用于处理从 ADFS vis WsFed 联合注销(客户端启动)。它或多或少直接来自 IdSrv3 文档:

 Notifications = new WsFederationAuthenticationNotifications()
            {
                RedirectToIdentityProvider = n =>
                {

                    if (n.ProtocolMessage.IsSignOutMessage)
                    {
                        var signOutMessageId = n.OwinContext.Environment.GetSignOutMessageId();
                        if (signOutMessageId != null)
                        {
                            n.OwinContext.Response.Cookies.Append("state", signOutMessageId);
                        }

                        var cleanUpUri =
                            $@"{n.Request.Scheme}://{n.Request.Host}{n.Request.PathBase}/external-signout-cleanup";

                        n.ProtocolMessage.Wreply = cleanUpUri;
                    }

                    return Task.FromResult(0);
                }
            }

最后,我的 /external-signout-cleanup 实现:

                coreApp.Map("/external-signout-cleanup", cleanup =>
                {
                    cleanup.Run(async ctx =>
                    {
                        var state = ctx.Request.Cookies["state"];
                        await ctx.Environment.RenderLoggedOutViewAsync(state);
                    });
                });