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);
});
});
我的网络应用程序是 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);
});
});