Oidc 客户端 js:静默访问令牌更新中断,因为身份服务器身份验证 cookie 滑动过期不起作用

Enr*_*one 5 openid-connect identityserver4 oidc-client-js angular

我正在使用一个 angular SPA,它通过使用身份服务器 4oidc 客户端 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"
                        }
                    }
Run Code Online (Sandbox Code Playgroud)

这是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();
        }
Run Code Online (Sandbox Code Playgroud)

services.AddAuthentication(x => x.DefaultAuthenticateScheme = IdentityServer4.IdentityServerConstants.DefaultCookieAuthenticationScheme);在阅读了这个 github 问题后,我添加了调用。根据我的理解,这个调用是多余的,因为调用services.AddIdentityServer应该已经将 cookie 身份验证设置为默认身份验证方案,使用常量IdentityServer4.IdentityServerConstants.DefaultCookieAuthenticationScheme作为身份验证方案名称。

通过使用此身份服务器配置,silen 访问令牌更新无法按我预期的方式工作。

访问令牌以静默方式更新 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 更新已按预期工作了 14 次):

在此处输入图片说明

这些是身份服务器写入的服务器端日志,用于确认用户会话在第十五次尝试时已过期:

在此处输入图片说明

这些是/connect/authorize成功尝试更新访问令牌更新访问令牌的前 14 次尝试之一)期间调用端点时身份服务器返回的响应标头。请注意,有一个响应标头为idsrvcookie设置了一个新值:

在此处输入图片说明

这些是身份服务器在更新访问令牌失败尝试(更新访问令牌的第十五次尝试)/connect/authorize期间调用端点时返回的响应标头。请注意,cookie 已失效,因为其过期日期设置为 2019 年的过去日期:idsrv.session

在此处输入图片说明

我是否遗漏了静默访问令牌更新和身份验证 cookie 滑动到期之间的关系?

这是预期的行为吗?

有没有办法让静默访问令牌更新工作无需新用户登录交互?

2020 年 9 月 16 日更新

我终于设法解决了这个问题。

修复是将IdentityServer4.EntityFrameworknuget 包更新到最新的可用版本(4.1.0截至今天)。

所有详细信息都在我自己的 oidc-client-js github 存储库上的 github 问题中报告。

总而言之,cookie 滑动过期的奇怪行为的根本原因是身份服务器错误,由nuget 包的4.1.0发布IdentityServer4.EntityFramework修复,如发行说明中所指出的

Att*_*iqe 2

这是我根据您的要求进行的配置:

public void ConfigureServices(IServiceCollection services)
    {
        services.AddHealthChecks();

        services.AddControllersWithViews();

        services.AddCustomOptions(Configuration);

        services.AddCustomDbContext(Configuration)
            .ResolveDependencies();

        services.AddIdentityServer(options =>
                {
                    options.Authentication.CookieLifetime = AccountOptions.RememberMeLoginDuration;
                    options.Events.RaiseSuccessEvents = true;
                    options.Events.RaiseFailureEvents = true;
                    options.Events.RaiseErrorEvents = true;
                })
            .AddProfileService<ProfileService>()
            .AddSigningCertificate(Configuration)
            .AddInMemoryClients(Configuration.GetSection("IdentityServer:Clients"))
            .AddInMemoryIdentityResources(Resources.GetIdentityResources())
            .AddInMemoryApiResources(Resources.GetApis());

        var externalProviders = Configuration.GetSection(nameof(ApplicationOptions.ExternalProviders))
            .Get<ExternalProvidersOptions>();

        services.AddWindowsIdentityProvider(externalProviders.UseWindows);

        services.AddLocalization(options => options.ResourcesPath = Constants.Resources);

        services.AddMvc()
            .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
            .AddDataAnnotationsLocalization();

        services.AddMvcCore()
            .AddCustomCors();
    }
Run Code Online (Sandbox Code Playgroud)

另外,以下是应用程序设置中的客户端配置:

{
    "Enabled": true,
    "ClientId": "dashboard",
    "ClientName": "Web Client",
    "ClientSecrets": [ { "Value": "K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=" } ],
    "AllowedGrantTypes": [ "implicit", "authorization_code" ],
    "AllowedScopes": [ "openid", "email", "profile", "role" ],
    "AllowOfflineAccess": true,
    "AllowAccessTokensViaBrowser": true,
    "AllowedCorsOrigins": [
      "http://localhost:7004"
    ],
    "RedirectUris": [
      "http://localhost:7004/callback",
      "http://localhost:7004/refreshtoken"
    ],
    "PostLogoutRedirectUris": [
      "http://localhost:7004"
    ],
    "AccessTokenLifetime": 3600,
    "RequireConsent": false
  }
Run Code Online (Sandbox Code Playgroud)