部署到 Azure 应用服务时 IdentityServer Front Channel 注销出现问题

时间:2021-02-04 14:36:06

标签: azure azure-web-app-service identityserver4

我在部署到 Azure 应用服务时遇到 IdentityServer Front Channel Logout 问题。我已将三个应用程序(Idp 和两个 SP)配置为使用 Front Channel Logout,如下所示:

IdP 客户端配置:

        public static IEnumerable<Client> Clients =>
            new Client[]
            {
                new Client
                {
                    ClientId = "Authorization.Service.UI.DEV",
                    ClientName = "Authorization Service UI [Development]",

                    AllowedGrantTypes = GrantTypes.Code,
                    RequireClientSecret = false,
                    RequirePkce = true,
                    AllowOfflineAccess = true,

                    // where to redirect to after login
                    RedirectUris = new List<string>
                    {
                        "https://localhost:44305/signin-oidc",
                    },

                    // where to redirect to after logout
                    PostLogoutRedirectUris = new List<string>
                    {
                        "https://localhost:44305/signout-callback-oidc",
                    },
                    FrontChannelLogoutUri = "https://localhost:44305/Account/FrontChannelLogout",
                    FrontChannelLogoutSessionRequired = true,

                    AllowedScopes = new List<string>
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Email,
                        "Authorization.Service.API.Read",
                        "Authorization.Service.API.Write"
                    },

                    AccessTokenLifetime = Convert.ToInt32((new TimeSpan(1,0,0,0)).TotalSeconds)
                },
                new Client
                {
                    ClientId = "Authorization.Service.UI",
                    ClientName = "Authorization Service UI",

                    AllowedGrantTypes = GrantTypes.Code,
                    RequireClientSecret = false,
                    RequirePkce = true,
                    AllowOfflineAccess = true,

                    // where to redirect to after login
                    RedirectUris = new List<string>
                    {
                        "https://as-ui-cdcavell.azurewebsites.net/signin-oidc"
                    },

                    // where to redirect to after logout
                    PostLogoutRedirectUris = new List<string>
                    {
                        "https://as-ui-cdcavell.azurewebsites.net/signout-callback-oidc"
                    },
                    FrontChannelLogoutUri = "https://as-ui-cdcavell.azurewebsites.net/Account/FrontChannelLogout",
                    FrontChannelLogoutSessionRequired = true,

                    AllowedScopes = new List<string>
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Email,
                        "Authorization.Service.API.Read",
                        "Authorization.Service.API.Write"
                    },

                    AccessTokenLifetime = Convert.ToInt32((new TimeSpan(1,0,0,0)).TotalSeconds)
                },
                new Client
                {
                    ClientId = "cdcavell.name.DEV",
                    ClientName = "Personal Website of Christopher D. Cavell [Development]",

                    AllowedGrantTypes = GrantTypes.Code,
                    RequireClientSecret = false,
                    RequirePkce = true,
                    AllowOfflineAccess = true,

                    // where to redirect to after login
                    RedirectUris = new List<string>
                    {
                        "https://localhost:44349/signin-oidc",
                    },

                    // where to redirect to after logout
                    PostLogoutRedirectUris = new List<string>
                    {
                        "https://localhost:44349/signout-callback-oidc",
                    },
                    FrontChannelLogoutSessionRequired = true,
                    FrontChannelLogoutUri = "https://localhost:44349/Account/FrontChannelLogout",

                    AllowedScopes = new List<string>
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Email,
                        "Authorization.Service.API.Read"
                    },

                    AlwaysIncludeUserClaimsInIdToken = true,
                    AccessTokenLifetime = Convert.ToInt32((new TimeSpan(1,0,0,0)).TotalSeconds)
                },
                new Client
                {
                    ClientId = "cdcavell.name",
                    ClientName = "Personal Website of Christopher D. Cavell",
                    
                    AllowedGrantTypes = GrantTypes.Code,
                    RequireClientSecret = false,
                    RequirePkce = true,
                    AllowOfflineAccess = true,

                    // where to redirect to after login
                    RedirectUris = new List<string>
                    { 
                        "https://cdcavell.name/signin-oidc"
                    },

                    // where to redirect to after logout
                    PostLogoutRedirectUris = new List<string>
                    { 
                        "https://cdcavell.name/signout-callback-oidc"
                    },
                    FrontChannelLogoutSessionRequired = true,
                    FrontChannelLogoutUri = "https://cdcavell.name/Account/FrontChannelLogout",

                    AllowedScopes = new List<string>
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Email,
                        "Authorization.Service.API.Read"
                    },

                    AlwaysIncludeUserClaimsInIdToken = true,
                    AccessTokenLifetime = Convert.ToInt32((new TimeSpan(1,0,0,0)).TotalSeconds)
                }

            };

SP 注销操作:

        /// <summary>
        /// Logout method
        /// </summary>
        /// <returns>Task&lt;IActionResult&gt;</returns>
        /// <method>Logout()</method>
        [AllowAnonymous]
        [HttpGet]
        public async Task<IActionResult> Logout()
        {
            if (User.Identity.IsAuthenticated)
            {
                // Remove Authorization record
                Data.Authorization authorization = Data.Authorization.GetRecord(User.Claims, _dbContext);
                authorization.Delete(_dbContext);

                await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
                SignOut(CookieAuthenticationDefaults.AuthenticationScheme, "oidc");

                DiscoveryCache discoveryCache = (DiscoveryCache)HttpContext
                    .RequestServices.GetService(typeof(IDiscoveryCache));
                DiscoveryDocumentResponse discovery = discoveryCache.GetAsync().Result;
                if (!discovery.IsError)
                    return Redirect(discovery.EndSessionEndpoint);
            }

            return RedirectToAction("Index", "Home");
        }

        /// <summary>
        /// Front Channel SLO Logout method
        /// &lt;br /&gt;&lt;br /&gt;
        /// https://andersonnjen.com/2019/03/22/identityserver4-global-logout/
        /// </summary>
        /// <returns>Task&lt;IActionResult&gt;</returns>
        /// <method>FrontChannelLogout(string sid)</method>
        [AllowAnonymous]
        [HttpGet]
        public async Task<IActionResult> FrontChannelLogout(string sid)
        {
            if (User.Identity.IsAuthenticated)
            {
                var currentSid = User.FindFirst("sid")?.Value ?? "";
                if (string.Equals(currentSid, sid, StringComparison.Ordinal))
                {
                    await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
                }
            }

            return NoContent();
        }

在对在不同端口上运行的 localhost 进行测试时,两个 SP 中都会调用 FrontChannelLogout 操作,并且都从 IdP 注销。当代码部署到 Azure 应用服务时,只有发起注销的 SP 会被注销。第二个 SP 仍保持登录 IdP。

我认为这与内容安全策略有关,但在按如下方式配置 CSP 后仍然得到相同的结果:

IdP 内容安全政策:

 csp += "frame-ancestors 'self' https://cdcavell.name https://as-ui-cdcavell.azurewebsites.net; ";
 csp += "frame-src 'self'; ";

SP 内容安全政策:

 csp += "frame-ancestors 'self'; ";
 csp += "frame-src 'self' https://dis5-cdcavell.azurewebsites.net https://www.google.com; ";

想知道是否有人遇到过这种情况,或者这是否与 Azue App Service 配置有关?

完整来源:https://github.com/cdcavell/cdcavell.name

网站:https://cdcavell.name

更新:

我尝试了多种方法,例如为 samesite = none 问题添加修复

            // Override the CookieAuthenticationOptions for DefaultCookieAuthenticationScheme
            // https://github.com/IdentityServer/IdentityServer4/blob/c30de032ec1dedc3b17dfa342043850638e84b43/src/IdentityServer4/src/Configuration/DependencyInjection/ConfigureInternalCookieOptions.cs#L28
            services.Configure<CookieAuthenticationOptions>(IdentityServerConstants.DefaultCookieAuthenticationScheme, options =>
            {
                options.Cookie.SameSite = SameSiteMode.None;
                options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
                options.Cookie.IsEssential = true;
            });

以及禁用 Arr Session Affinity cookie

    <httpProtocol>
        <customHeaders>
            <add name="Arr-Disable-Session-Affinity" value="true"/>
        </customHeaders>
    </httpProtocol>

这些都没有奏效,所以我放弃 FrontChanelLogout 并尝试使用 damienbod's article 概述的 Redis 缓存实现 BackChanelLogout

2 个答案:

答案 0 :(得分:0)

当您调用退出时,您不应返回自己的结果或返回查看。

相反,操作方法应如下所示:

    public async Task DoLogout()
    {
        await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
        await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
    }

这是因为 SignOutAsync 创建了自己的响应,当您返回响应时,您会覆盖此内部响应。

答案 1 :(得分:0)

问题已解决:

这是由于混合域(cdcavell.name 和 azurewebsites.net)造成的。一个应用服务在 cdcavell.name 下,另外两个在 azurewebsites.net 下。

在 Azure 应用服务中设置自定义域,然后添加通配符 SSL 绑定。