【发布时间】:2021-01-02 04:48:30
【问题描述】:
我正在使用 Angular SPA,它通过使用 identity server 4 和 oidc client js 来实现身份验证。
在静默访问令牌更新级别出现问题。预期的行为是访问令牌的自动更新,这要归功于调用 /connect/authorize 端点的 iframe。此调用将身份服务器身份验证 cookie 与 HTTP 请求一起发送,这样做身份服务器知道用户会话仍然有效并且能够在没有的情况下颁发新的访问令牌> 要求用户以交互方式再次登录。到目前为止,我很确定我的理解是好的。
这里是棘手的部分:我的期望是身份服务器身份验证cookie应该有一个滑动到期,以便每次调用/connect/authorize时它的到期日期都会及时向前移动端点被制成。换句话说,我预计在用户第一次登录后不需要用户进行其他交互式登录,因为用户会话到期日期每次静默更新 iframe 需要新的访问令牌。
为了获得这种行为,我在身份服务器级别设置了以下配置。
这是客户端配置(注意访问令牌的生命周期是 2 分钟 = 120 秒):
new Client
{
ClientId = "web-client",
ClientName = "SPA web client",
AllowedGrantTypes = GrantTypes.Code,
RequireClientSecret = false,
RequirePkce = true,
RequireConsent = false,
AccessTokenLifetime = 120,
RedirectUris = { "https://localhost:4200/assets/signin-callback.html", "https://localhost:4200/assets/silent-callback.html" },
PostLogoutRedirectUris = { "https://localhost:4200/signout-callback" },
AllowedCorsOrigins = { "https://localhost:4200" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"dataset",
"exercise",
"user-permissions"
}
}
这是ConfigureServices,我在其中添加了所有身份服务器配置。请注意,cookie 生命周期设置为 15 分钟,并且需要 cookie 滑动过期:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<RequestLoggingOptions>(o =>
{
o.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
diagnosticContext.Set("RemoteIpAddress", httpContext.Connection.RemoteIpAddress.MapToIPv4());
};
});
services.AddControllersWithViews();
var migrationsAssembly = GetRunningAssemblyName();
var connectionString = this.Configuration.GetConnectionString(IdentityServerDatabaseConnectionString);
var identityServerBuilder = services.AddIdentityServer(options =>
{
options.Authentication.CookieLifetime = TimeSpan.FromMinutes(15);
options.Authentication.CookieSlidingExpiration = true;
})
.AddTestUsers(TestData.Users)
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = dbContextBuilder =>
dbContextBuilder.UseSqlServer(
connectionString,
sqlServerOptionsBuilder => sqlServerOptionsBuilder.MigrationsAssembly(migrationsAssembly)
);
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = dbContextBuilder =>
dbContextBuilder.UseSqlServer(
connectionString,
sqlServerOptionsBuilder => sqlServerOptionsBuilder.MigrationsAssembly(migrationsAssembly)
);
});
services.AddAuthentication(x => x.DefaultAuthenticateScheme = IdentityServer4.IdentityServerConstants.DefaultCookieAuthenticationScheme);
identityServerBuilder.AddDeveloperSigningCredential();
}
在阅读this github issue 后,我已将呼叫添加到services.AddAuthentication(x => x.DefaultAuthenticateScheme = IdentityServer4.IdentityServerConstants.DefaultCookieAuthenticationScheme);。根据我的理解,这个调用是多余的,因为对 services.AddIdentityServer 的调用应该已经将 cookie 身份验证设置为默认身份验证方案,使用常量 IdentityServer4.IdentityServerConstants.DefaultCookieAuthenticationScheme 作为身份验证方案名称。
通过使用此身份服务器配置,静默访问令牌更新不会按我预期的方式工作。
访问令牌被静默更新 14 次,然后第十五次尝试更新访问令牌失败并显示消息 SilentRenewService._tokenExpiring: Error from signinSilent: login_required。
这基本上意味着身份验证 cookie 滑动过期不起作用,因为我的身份验证 cookie 有 15 分钟的生命周期,我的 SPA 客户端的访问令牌有 2 分钟的生命周期,并且 oidc 客户端 js 库正在执行静默刷新周期每分钟一次(访问令牌在其到期时间前 60 秒更新,因此使用我的设置,每分钟完成一次静默更新)。在第十五次尝试更新访问令牌时,身份验证 cookie 最终过期,身份服务器授权端点向https://localhost:4200/assets/silent-callback.html 静态页面返回错误响应。
这些是我的控制台日志(请注意,silen renew 已按预期工作了 14 次):
这些是身份服务器写入的服务器端日志,确认用户会话在第十五次尝试时过期:
这些是身份服务器在成功尝试更新访问令牌(更新访问令牌的前 14 次尝试之一)期间调用 /connect/authorize 端点时返回的响应标头。请注意,有一个响应标头为 idsrv cookie 设置了一个新值:
这些是在尝试更新访问令牌失败(第十五次尝试更新访问令牌)期间调用/connect/authorize 端点时身份服务器返回的响应标头。请注意,idsrv.session cookie 已失效,因为其过期日期设置为 2019 年的过去日期:
我是否遗漏了有关静默访问令牌更新和身份验证 cookie 滑动到期之间的关系的任何信息?
这是预期的行为吗?
有没有办法让静默访问令牌更新工作而不需要新的用户登录交互?
2020 年 9 月 16 日更新
我终于设法解决了这个问题。
修复方法是将IdentityServer4.EntityFramework nuget 包更新为最新可用版本(截至今天为4.1.0)。
所有详情都报告in my own github issue on the oidc-client-js github repository。
总而言之,cookie 滑动过期的奇怪行为的根本原因是 identity server bug,由 4.1.0 发布的 IdentityServer4.EntityFramework nuget 包修复,正如 release notes 中所指出的那样。
【问题讨论】:
-
非常感谢您提供如此详细的描述。对我很有帮助。
标签: angular identityserver4 openid-connect oidc-client-js