我在部署到 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<IActionResult></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
/// <br /><br />
/// https://andersonnjen.com/2019/03/22/identityserver4-global-logout/
/// </summary>
/// <returns>Task<IActionResult></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
更新:
我尝试了多种方法,例如为 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
答案 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 绑定。