我有一个ASP.NET MVC应用程序,我正在使用ASP.NET Identity 2.我有一个奇怪的问题。每个请求浏览器都会调用ApplicationUser.GenerateUserIdentityAsync
到我的网站。我添加了一些Trace.WriteLine
,这是删除IIS输出后的结果:
IdentityConfig.Configuration called
ApplicationUser.GenerateUserIdentityAsync called url: http://localhost:54294/
ApplicationUser.GenerateUserIdentityAsync called url: http://localhost:54294/Content/bootstrap.css
ApplicationUser.GenerateUserIdentityAsync called url: http://localhost:54294/Scripts/modernizr-2.8.3.js
ApplicationUser.GenerateUserIdentityAsync called url: http://localhost:54294/Content/site.css
ApplicationUser.GenerateUserIdentityAsync called url: http://localhost:54294/Scripts/jquery-2.1.3.js
ApplicationUser.GenerateUserIdentityAsync called url: http://localhost:54294/Scripts/bootstrap.js
ApplicationUser.GenerateUserIdentityAsync called url: http://localhost:54294/Scripts/respond.js
ApplicationUser.GenerateUserIdentityAsync called url: http://localhost:54294/Scripts/script.js
ApplicationUser.GenerateUserIdentityAsync called url: http://localhost:54294/Glimpse.axd?n=glimpse_client&hash=8913cd7e
ApplicationUser.GenerateUserIdentityAsync called url: http://localhost:54294/Glimpse.axd?n=glimpse_metadata&hash=8913cd7e&callback=glimpse.data.initMetadata
ApplicationUser.GenerateUserIdentityAsync called url: http://localhost:54294/Glimpse.axd?n=glimpse_request&requestId=6171c2b0-b6e5-4495-b495-4fdaddbe6e8f&hash=8913cd7e&callback=glimpse.data.initData
ApplicationUser.GenerateUserIdentityAsync called url: http://localhost:54294/Glimpse.axd?n=glimpse_sprite&hash=8913cd7e
ApplicationUser.GenerateUserIdentityAsync called url: http://localhost:54294/__browserLink/requestData/38254292a54f4595ad26158540adbb6a?version=2
如果我运行由模板创建的默认MVC应用程序,我得到了这个:
IdentityConfig.Configuration called
并且只有当我登录时,它才会调用ApplicationUser.GenerateUserIdentityAsync
。
我到处都看到了我认为可能但我没有找到任何结果。我正在使用(如果它有帮助)
StructureMap 3
Elmah
Glimpse
ASP.NET MVC 5
EF6
ASP.NET Identity 2
其他信息
我在不使用UserManage的情况下将用户直接添加到数据库中。我不确定它是否与身份有任何问题。
更新
我已经删除了数据库,它不再发生了。发生了什么事?
更新2
它发生在我的谷歌浏览器中(我使用glimpse监视SQL连接)并且在删除存储的cookie之后,它没有发生。可以登录其他浏览器导致此问题吗?
更新3
同时注销 - 登录似乎可以暂时解决问题。
答案 0 :(得分:3)
我遇到了同样的问题,在深入研究源代码和一些侦探工作后,我找到了解决方案。问题出在SecurityStampValidator
内,用作默认的OnValidateIdentity
处理程序。查看源代码here。有趣的部分:
var issuedUtc = context.Properties.IssuedUtc;
// Only validate if enough time has elapsed
var validate = (issuedUtc == null);
if (issuedUtc != null)
{
var timeElapsed = currentUtc.Subtract(issuedUtc.Value);
validate = timeElapsed > validateInterval;
}
此部分针对每个请求运行,如果validate
为真,则调用getUserIdCallback
和regenerateIdentityCallback
(在跟踪输出中可见)。这里的问题是issuedUtc
始终是创建Cookie的日期,因此validate
在validateInterval
过去时始终为true。这解释了您遇到的奇怪行为。如果validateInterval
为10分钟,则会在创建cookie后10分钟或更长时间内为每个请求运行验证逻辑(部署应用程序,清除cookie,在重新登录时重置cookie)。
SecurityStampValidator
应根据之前的验证日期(或第一次检查时的发布日期)做出是否验证的决定,但它没有这样做。为了使issuedUtc
日期向前推进,有3种可能的解决方案:
validateInterval
的Cookie,这意味着SingOut
和SignIn
。类似的解决方案here。这似乎是一项代价高昂的操作,尤其是validateInterval
只设置为几分钟。CookieAuthenticationOptions.SlidingExpiration
逻辑自动重新发布Cookie。在this post中解释得非常好。如果SlidingExpiration设置为true,那么将在ExpireTimeSpan中途的任何请求上重新发出cookie。例如,如果用户登录并在16分钟后再发出第二个请求,则会再次发出cookie 30分钟。如果用户登录,然后在31分钟后发出第二个请求,则会提示用户登录。
在我的情况下(内部网应用程序),用户在30分钟不活动后注销是不可接受的。我需要默认ExpireTimeSpan
,这是14天。所以这里的选择是实现某种ajax轮询来延长cookie的生命周期。听起来很难完成这个相当简单的场景。
我最后选择使用的最后一个选项是修改SecurityStampValidator
实现以获得滑动验证方法。示例代码如下。请务必在Startup.Auth.cs中将SecurityStampValidator
替换为SlidingSecurityStampValidator
。我将IdentityValidationDates
字典添加到原始实现中以存储每个用户的验证日期,然后在检查是否需要验证时使用它。
public static class SlidingSecurityStampValidator
{
private static readonly IDictionary<string, DateTimeOffset> IdentityValidationDates = new Dictionary<string, DateTimeOffset>();
public static Func<CookieValidateIdentityContext, Task> OnValidateIdentity<TManager, TUser, TKey>(
TimeSpan validateInterval, Func<TManager, TUser, Task<ClaimsIdentity>> regenerateIdentityCallback,
Func<ClaimsIdentity, TKey> getUserIdCallback)
where TManager : UserManager<TUser, TKey>
where TUser : class, IUser<TKey>
where TKey : IEquatable<TKey>
{
if (getUserIdCallback == null)
{
throw new ArgumentNullException(nameof(getUserIdCallback));
}
return async context =>
{
var currentUtc = DateTimeOffset.UtcNow;
if (context.Options != null && context.Options.SystemClock != null)
{
currentUtc = context.Options.SystemClock.UtcNow;
}
var issuedUtc = context.Properties.IssuedUtc;
// Only validate if enough time has elapsed
var validate = issuedUtc == null;
if (issuedUtc != null)
{
DateTimeOffset lastValidateUtc;
if (IdentityValidationDates.TryGetValue(context.Identity.Name, out lastValidateUtc))
{
issuedUtc = lastValidateUtc;
}
var timeElapsed = currentUtc.Subtract(issuedUtc.Value);
validate = timeElapsed > validateInterval;
}
if (validate)
{
IdentityValidationDates[context.Identity.Name] = currentUtc;
var manager = context.OwinContext.GetUserManager<TManager>();
var userId = getUserIdCallback(context.Identity);
if (manager != null && userId != null)
{
var user = await manager.FindByIdAsync(userId);
var reject = true;
// Refresh the identity if the stamp matches, otherwise reject
if (user != null && manager.SupportsUserSecurityStamp)
{
var securityStamp = context.Identity.FindFirstValue(Constants.DefaultSecurityStampClaimType);
if (securityStamp == await manager.GetSecurityStampAsync(userId))
{
reject = false;
// Regenerate fresh claims if possible and resign in
if (regenerateIdentityCallback != null)
{
var identity = await regenerateIdentityCallback.Invoke(manager, user);
if (identity != null)
{
// Fix for regression where this value is not updated
// Setting it to null so that it is refreshed by the cookie middleware
context.Properties.IssuedUtc = null;
context.Properties.ExpiresUtc = null;
context.OwinContext.Authentication.SignIn(context.Properties, identity);
}
}
}
}
if (reject)
{
context.RejectIdentity();
context.OwinContext.Authentication.SignOut(context.Options.AuthenticationType);
}
}
}
};
}
}
答案 1 :(得分:0)
有两个可能导致重置的问题:
issuedUtc
属性为null
)在startup.cs
课程中,您将找到Cookie身份验证配置。在配置中是一个函数委托,应该像下面这样设置:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
...
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
...
}
}
检查您的validateInterval
未设置为较低值。在上述情况下,在发布有效cookie后30分钟将会有一个数据库(user.GenerateUserIdentityAsync
)调用。在你的情况下,它可能像每秒一样设置为低值。
如果您正在使用logout everywhere功能(安全标记),则validateInterval
的更改将允许cookie在调用OnValidateIdentity
函数之前保持有效。