我有一个具有asp .net身份的身份服务器4应用程序。我设置了可滑动的Cookie。
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));
这导致一些非常有趣的信息立即显示在我的日志中。
安全戳验证失败:[principalSecurityStamp(空)]!= [getSecurityStampAsyncResults 83270b3f-a042-4a8f-b090-f5e1a084074e]也([userManagerSecurityStamp 83270b3f-a042-4a8f-b090-f5e1a084074e])
因此principal.FindFirstValue(Options.ClaimsIdentity.SecurityStampClaimType)
似乎为空。为什么我不知道。我也不知道如何解决它,因为有许多第三方应用程序在调用此身份服务器。
update2:
我现在可以验证GenerateClaimsAsync确实设置了SecurityStampClaim。但是ValidateAsync中的CookieValidatePrincipalContext不包含有问题的声明,这很奇怪,因为对该方法的注释说。
/// <param name="context">The context containing the <see cref="System.Security.Claims.ClaimsPrincipal"/>
答案 0 :(得分:0)
“ SlidingExpiration设置为true,以指示处理程序在处理超过有效期窗口一半以上的请求时,以新的有效期重新发出新的cookie。”对于30天的有效期窗口,在前15天不会发布新的Cookie。 15天后的第一个请求将发出刷新的cookie。
30分钟超时可能来自security stamp validator,{30}仅每30分钟运行一次(验证费用很高)。听起来您的邮票是错误生成或验证的。您是否完全配置或自定义了该组件?
旁注:删除opts.Cookie.Expiration,它将被忽略。
答案 1 :(得分:0)
要花很长时间才能弄清这个问题的根源。如果其他人遇到此问题,我将尝试在这里进行解释。
首先,问题是安全令牌。安全令牌存储在用户表中。创建identity.application cookie时,此令牌存储在cookie中。应用程序每五分钟与身份服务器联系一次,并检查是否需要验证令牌。如果它的时间超过30分钟,则将验证安全令牌。 (请注意,五分钟和三十分钟的时间都是可配置的,这只是默认设置)
这用于在任何地方进行注销。如果您更改密码,则用户表中行上的安全令牌将被更新。通过使其不同于所有设备中Cookie中存储的内容来实现。这将迫使您在任何地方注销。
第一个问题
SignInManager.cs#L260验证安全令牌,但不测试它是否为空。
因此,如果cookie出了点问题,并且令牌由于某种原因在数据库中或者由于我的原因令牌已为null,而该令牌已被另一个cookie覆盖,则用户将登录30分钟然后被踢出第一次尝试验证安全令牌。导致发布请求#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);