所以这里有几个可能的解决方案:
- 调用 OIDC UserInfo Endpoint 以获取每个请求的更新用户声明
- 降低 cookie 生命周期以更频繁地自动刷新用户信息
- 在 IdentityServer 上实现一个自定义端点,以便将配置文件更改信息发布到订阅的客户端列表(例如您的 web 应用程序)。
- 在更改用户配置文件数据时让 IdentityServer 强制单点注销
就实现难度而言,降低 cookie 生命周期是最简单的(只是更改 cookie 过期时间),但它不能保证声明是最新的,并且对用户可见(经常重定向到 IdentityServer,虽然如果访问令牌生命周期仍然有效,则无需登录)
让 webapp 在每个请求上调用 UserInfo 端点是第二简单的(参见下面的示例),但对性能的影响最差。每个请求都会产生到 IdentityServer 的往返。
端点/订阅者模型的性能开销最低。仅当用户配置文件信息实际更改时才会发生对 IdentityServer 的 UserInfo 请求。这实现起来会有点复杂:
- 在您的 IdentityServer 项目中,您需要修改对配置文件数据的更改,并将 http 消息发布到您的 web 应用程序。该消息可以简单地包含修改后用户的用户 ID。此消息需要以某种方式进行身份验证,以防止恶意用户使合法用户会话无效。您可以为此包含一个 ClientCredentials 不记名令牌。
- 您的 web 应用程序需要接收和验证消息。它需要将更改后的用户 ID 存储在 OnValidatePrincipal 委托可访问的位置(很可能通过 DI 容器中的服务)
- Cookie OnValidatePrincipal 委托将注入此本地服务,以在验证主体之前检查用户信息是否已更改
代码示例
在每次调用时从端点获取更新的用户信息
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "NameOfYourCookieAuthSchemeHere",
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async context =>
{
// Get updated UserInfo from IdentityServer
var accessToken = context.Principal.Claims.FirstOrDefault(c => c.Type == "access_token").Value;
var userInfoClient = new UserInfoClient("https://{IdentityServerUrlGoesHere}");
var userInfoResponse = await userInfoClient.GetAsync(accessToken);
// Invalidate Principal if Error Response
if (userInfoResponse.IsError)
{
context.RejectPrincipal();
await context.HttpContext.Authentication.SignOutAsync("NameOfYourCookieAuthSchemeHere");
}
else
{
// Check if claims changed
var claimsChanged = userInfoResponse.Claims.Except(context.Principal.Claims).Any();
if (claimsChanged)
{
// Update claims and replace principal
var newIdentity = context.Principal.Identity as ClaimsIdentity;
newIdentity.AddClaims(userInfoResponse.Claims);
var updatedPrincipal = new ClaimsPrincipal();
context.ReplacePrincipal(updatedPrincipal);
context.ShouldRenew = true;
}
}
}
}
});
更新来自 IdentityServer 的订阅更改消息。此示例假设您创建了一个服务(例如 IUserChangedService),该服务存储在端点从 IdentityServer 接收到的用户 ID。我没有 webapp 的接收端点或服务的示例。
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "NameOfYourCookieAuthSchemeHere",
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async context =>
{
// Get User ID
var userId = context.Principal.Claims.FirstOrDefault(c => c.Type == "UserIdClaimTypeHere");
var userChangedService = context.HttpContext.RequestServices.GetRequiredService<IUserChangedService>();
var userChanged = await userChangedService.HasUserChanged(userId);
if (userChanged)
{
// Make call to UserInfoEndpoint and update ClaimsPrincipal here. See example above for details
}
}
}
});
除了使用本地数据库外,asp.net 核心文档也有一个示例。连接到 OnValidatePrincipal 方法的方法是相同的:
https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie#reacting-to-back-end-changes
希望这会有所帮助!