问题描述
在 ASP.NET Core 2 中使用 Cookie 身份验证(有或没有 Identity
)时,可能会发生用户的电子邮件或姓名更改,甚至帐户在 cookie 的生命周期内被删除的情况.这就是为什么 docs 指出,应该验证 cookie.文档中的示例用
When using Cookie Authentication in ASP.NET Core 2 (with or without Identity
) it might happen, that a user's email or name is changed, or even the account is deleted during the cookie's lifetime. That's why the docs point out, that the cookie should be validated. The example in the docs is commented with
此处描述的方法在每次请求时都会触发.这个可以导致应用性能大幅下降.
所以我想知道验证 cookie 主体的最佳模式是什么.我在 Startup.cs
中所做的是订阅 OnValidatePrincipal
事件并检查主体的有效性,例如每 5 分钟向 cookie 附加一个 LastValidatedOn
声明,如下所示:
So I am wondering what is the best pattern to validate the cookie principal. What I did in Startup.cs
is to subscribe to the OnValidatePrincipal
event and check the pricipal's validity e.g. every 5 minutes by appending a LastValidatedOn
claim to the cookie like so:
services.ConfigureApplicationCookie(options =>
{
// other cookie options go here
options.Events.OnValidatePrincipal = async context =>
{
const string claimType = "LastValidatedOn";
const int reValidateAfterMinutes = 5;
if (!(context.Principal?.Identity is ClaimsIdentity claimIdentity)) return;
if (!context.Principal.HasClaim(c => c.Type == claimType) ||
DateTimeOffset.Now.UtcDateTime.Subtract(new DateTime(long.Parse(context.Principal.Claims.First(c => c.Type == claimType).Value))) > TimeSpan.FromMinutes(reValidateAfterMinutes))
{
var mgr = context.HttpContext.RequestServices.GetRequiredService<SignInManager<ApplicationUser>>();
var user = await mgr.UserManager.FindByNameAsync(claimIdentity.Name);
if (user != null && claimIdentity.Claims.FirstOrDefault(c => c.Type == "AspNet.Identity.SecurityStamp")?.Value == await mgr.UserManager.GetSecurityStampAsync(user))
{
claimIdentity.FindAll(claimType).ToList().ForEach(c => claimIdentity.TryRemoveClaim(c));
claimIdentity.AddClaim(new Claim(claimType, DateTimeOffset.Now.UtcDateTime.Ticks.ToString(), typeof(long).ToString()));
context.ShouldRenew = true;
}
else
{
context.RejectPrincipal();
await mgr.SignOutAsync();
}
}
};
});
推荐答案
@MarkG 为我指明了正确的方向,谢谢.仔细查看源代码后 对于 SecurityStampValidator
和 Identity
事情对我来说变得很清楚.实际上,我在问题中发布的示例代码是不必要的,因为 ASP.NET Core Identity 以更好的方式提供了开箱即用的功能.
@MarkG pointed me into the right direction, thanks. After having a closer look at the source code for SecurityStampValidator
and Identity
things became clear to me. Actually, the sample code I posted with my question is unnecessary, because ASP.NET Core Identity brings the feature in a better fashion out-of-the-box.
因为我还没有找到这样的摘要,也许它对其他人也有帮助.
As I didn't find a summary like this yet, maybe it will be helpful to others, too.
...但还是很高兴知道...
... but still good to know...
services.ConfigureApplicationCookie(options =>
{
options.Cookie.Expiration = TimeSpan.FromDays(30);
options.ExpireTimeSpan = TimeSpan.FromDays(30);
options.SlidingExpiration = true;
});
ExpireTimeSpan
默认为 TimeSpan.FromDays(14)
身份验证票的签发时间是 cookie (CookieValidatePrincipalContext.Properties.IssuedUtc
) 的一部分.当 cookie 被发送回服务器时,当前时间减去发布时间必须大于ExpireTimeSpan
.如果不是,用户将被注销而无需进一步调查.在实践中,设置ExpireTimeSpan
,主要是与SlidingExpiration
设置为true
一起使用.这是一种确保用户积极使用应用程序的方法,而不是例如让设备无人看管.负 TimeSpan
s 将立即注销用户(但不是 TimeSpan.Zero
).
The issue time of the authentication ticket is part of the cookie (CookieValidatePrincipalContext.Properties.IssuedUtc
). When the cookie is sent back to the server, the current time minus the issue time must be greater than ExpireTimeSpan
. If it is not, the user will be signed out without further investigation. In practice, setting the ExpireTimeSpan
, mostly goes together with SlidingExpiration
set to true
. This is a means to ensure that the user is actively working with the app, and did not e.g. leave the device back unattended. Negative TimeSpan
s will sign the user off immediately (but not TimeSpan.Zero
).
services.AddOptions();
services.Configure<SecurityStampValidatorOptions>(options =>
{
// This is the key to control how often validation takes place
options.ValidationInterval = TimeSpan.FromMinutes(5);
});
验证间隔
默认为 TimeSpan.FromMinutes(30)
这决定了验证 cookie 的有效性将根据持久存储进行检查的时间跨度.它是通过为服务器的每个请求调用SecurityStampValidator
来完成的.如果当前时间减去 cookie 的发布时间小于或等于 ValidationInterval
,将调用 ValidateSecurityStampAsync
.这意味着ValidationInterval = TimeSpan.Zero
导致为每个请求调用 ValidateSecurityStampAsync
.
This determines the time span after which the validity of the authentication cookie will be checked against persistent storage. It is accomplished by calling the SecurityStampValidator
for every request to the server. If the current time minus the cookie's issue time is less or equal to ValidationInterval
, a call to ValidateSecurityStampAsync
will occur. This meansValidationInterval = TimeSpan.Zero
leads to calling the ValidateSecurityStampAsync
for each request.
注意 UserManager
必须支持获取安全戳,否则会失败.对于自定义用户管理器或用户存储,两者都必须正确实现 IUserSecurityStampStore
.
Note UserManager
must support getting security stamps or it will fail. For a custom user manager or user store, both must properly implement IUserSecurityStampStore<TUser>
.
需要注意的是:services.AddIdentity()
还为身份验证 cookie 设置默认值.如果您在 services.ConfigureApplicationCookie()
之后添加它,这将覆盖之前的设置.我在上述之前调用了 services.Configure()
.
The thing to be aware of is: services. AddIdentity()
also sets defaults for the authentication cookie. If you add it after services.ConfigureApplicationCookie()
this will override the previous settings.I called services.Configure<SecurityStampValidatorOptions>()
after the previous ones above.
再次感谢@MarkG 为我们指明方向.
Thanks again to @MarkG for showing the way.
这篇关于使用 ASP.NET Core 2.1/3+ Identity 验证身份验证 cookie的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!