Cookie 不会在服务器上滑动,但会在 运行 本地滑动

Cookie does not slide on server but does when run locally

我有一个具有 asp .net 标识的身份服务器 4 应用程序。我已将饼干设置为滑动。

services.ConfigureApplicationCookie(opts =>
                        {
                            opts.Cookie.Expiration = TimeSpan.FromDays(30);
                            opts.SessionStore = new RedisCacheTicketStore(new RedisCacheOptions()
                            {
                                Configuration = configuration["Redis:HostPort"]
                            }, logger, configuration);
                            opts.Cookie.SameSite = SameSiteMode.None;
                            opts.SlidingExpiration = true;
                            opts.ExpireTimeSpan = TimeSpan.FromDays(30);
                        }
                    );

不滑动

Localhost:当用户登录时.AspNetCore.Idenitty.Application得到一个过期时间。当页面刷新时,到期时间被更新,我可以看到时间戳发生变化。

生产:但是,如果我在服务器上启动时检查它,则用户登录并且。AspNetCore.Idenitty.Application 获得带有登录时间戳的过期时间。但是当页面刷新时时间戳不会改变。它与用户登录时保持一致。

用户在 30 分钟后被踢出

生产:第二个问题是,如您所见,过期时间提前一个月设置,但在 30 分钟后在服务器上时,该用户将被踢出并强制重新登录。我不能让用户登录超过 30 分钟,即使他们处于活动状态。

安全戳

我检查过用户安全标记没有改变,令牌包含 "AspNet.Identity.SecurityStamp": "[users actual key]"

更新

所以经过一番挖掘,我最终决定超越安全标记验证。我通过在我的 ApplicationSignInManager

中覆盖以下方法来做到这一点
 public override async Task<ApplicationUser> ValidateSecurityStampAsync(ClaimsPrincipal principal)
        {
            if (principal == null)
            {
                Logger.LogError(LoggingEvents.ApplicationSignInManagerSecurityTokenValidation, "ClaimsPrincipal is null");
                return null;
            }
            var user = await UserManager.GetUserAsync(principal);
            if (await ValidateSecurityStampAsync(user, principal.FindFirstValue(Options.ClaimsIdentity.SecurityStampClaimType)))
            {
                return user;
            }

            if(user == null)
                Logger.LogError(LoggingEvents.ApplicationSignInManagerSecurityTokenValidation, "User not found [principal {principal}]", principal);

            var principalSecurityStamp = principal.FindFirstValue(Options.ClaimsIdentity.SecurityStampClaimType);  // Security stamp from claims
            var userManagerSecurityStamp = user.SecurityStamp;                                                     // Security Stamp from usermanager
            var getSecurityStampAsyncResults = await UserManager.GetSecurityStampAsync(user);                      // Security stamp from GetSecurityStampAsync
            Logger.LogError(LoggingEvents.ApplicationSignInManagerSecurityTokenValidation,
                "Security stamp Validation Failed: [principalSecurityStamp {principalSecurityStamp}] != [getSecurityStampAsyncResults {getSecurityStampAsyncResults}] also ([userManagerSecurityStamp {userManagerSecurityStamp}] )", principalSecurityStamp, getSecurityStampAsyncResults, userManagerSecurityStamp);

            return null;
        }

        public virtual async Task<bool> ValidateSecurityStampAsync(ApplicationUser user, string securityStamp)
            => user != null &&
               // Only validate the security stamp if the store supports it
               (!UserManager.SupportsUserSecurityStamp || securityStamp == await UserManager.GetSecurityStampAsync(user));

这导致一些非常有趣的信息立即出现在我的日志中。

Security stamp Validation Failed: [principalSecurityStamp (null)] != [getSecurityStampAsyncResults 83270b3f-a042-4a8f-b090-f5e1a084074e] also ([userManagerSecurityStamp 83270b3f-a042-4a8f-b090-f5e1a084074e] )

因此 principal.FindFirstValue(Options.ClaimsIdentity.SecurityStampClaimType) 似乎为空。为什么我不知道。我也不知道如何修复它,因为有许多第三方应用程序调用此身份服务器。

更新2:

我现在可以验证 GenerateClaimsAsync 确实设置了 SecurityStampClaim。但是,ValidateAsync 中的 CookieValidatePrincipalContext 不包含有问题的声明,正如对该方法的评论所说,这很奇怪。

/// <param name="context">The context containing the <see cref="System.Security.Claims.ClaimsPrincipal"/>

"The SlidingExpiration is set to true to instruct the handler to re-issue a new cookie with a new expiration time any time it processes a request which is more than halfway through the expiration window." 对于 30 天到期 window 的前 15 天不会发布新的 cookie。 15 天后的第一个请求将发出一个刷新的 cookie。

30 分钟的超时可能来自 security stamp validator,它每 30 分钟才运行一次(验证成本很高)。听起来您的图章生成或验证不正确。您是否配置或自定义了该组件?

旁注:删除 opts.Cookie.Expiration,它已被忽略。

我们花了很长时间才找到这个问题的根源。如果其他人遇到此问题,我将尝试在此处进行解释。

首先问题出在安全令牌上。安全令牌存储在用户 table 上,当创建 identity.application cookie 时,此令牌存储在 cookie 中。应用程序每五分钟联系身份服务器并检查令牌是否需要验证。如果它早于三十分钟,则将验证安全令牌。 (注意五分钟和三十分钟时间都是可配置的,这只是默认值)

这用于所谓的注销。如果您更改密码,用户 table 中您所在行的安全令牌将被更新。通过使它不同于存储在您所有设备上的 cookie 中的那个。这将迫使您在任何地方注销。

第一个问题

SignInManager.cs#L260 验证安全令牌但不测试它是否为空。

因此,如果 cookie 有问题并且令牌由于某种原因在数据库中为空,或者在我的情况下它已被另一个 cookie 覆盖,那么用户将登录三十分钟然后它在第一次尝试验证安全令牌时被踢出。这导致发布请求 #7055。每次都应测试 cookie 以确保其中包含安全令牌。

第 2 期

以下代码行让用户登录并创建将安全令牌存储在所述 cookie 中的 cookie

var signInUserResult = await _signInManager.PasswordSignInAsync(userName, password, rememberMe, true);

经过大量挖掘和调试后,我发现以下行用不包含安全令牌的新 cookie 覆盖了原始 cookie。

await HttpContext.SignInAsync(user.Id.ToString(), user.UserName, props);