我使用自定义实现的microsoft asp.net身份,因为我有自定义表,这就是为什么我给了自定义实现的所有方法IUserStore和IUserPasswordStore。
问题是当用户登录,然后在10 - 15分钟后登录用户会话过期,但我想要的是,除非用户注销,我想保持用户登录系统
代码:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
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))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
}
}
账户控制器:
[Authorize]
public class AccountController : Controller
{
public AccountController()
: this(new UserManager<UserModel>(new UserStore()))
{
}
public AccountController(UserManager<UserModel> userManager)
{
UserManager = userManager;
}
public UserManager<UserModel> UserManager { get; private set; }
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(string email, string password, bool rememberMe = false, string returnUrl = null)
{
if (ModelState.IsValid)
{
var user = UserManager.Find(email, password);
if (user != null)
{
await SignInAsync(user, rememberMe);
return RedirectToLocal(returnUrl);
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
return View();
}
private async Task SignInAsync(UserModel user, bool isPersistent)
{
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
identity.AddClaim(new Claim("FullName", user.FirstName + " " + user.LastName));
identity.AddClaim(new Claim("Email", user.Email));
identity.AddClaim(new Claim("Role", user.Role));
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent, ExpiresUtc = DateTime.UtcNow.AddDays(7) }, identity);
}
private IAuthenticationManager AuthenticationManager
{
get
{
return HttpContext.GetOwinContext().Authentication;
}
}
}
web . config:
<system.web>
<authentication mode="None" />
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
</system.web>
<system.webServer>
<modules>
<remove name="FormsAuthentication" />
</modules>
</system.webServer>
现在在下面的行中,我给出了7天的到期时间,但仍然会话在10 - 15分钟内到期:
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent, ExpiresUtc = DateTime.UtcNow.AddDays(7) }, identity);
在我下面的问题中,你会发现我的UserModel,自定义UserStore类,但为了保持这个问题小,我没有把代码放在这里:
UserModel和UserStore
更新:我已经完全排除了ApplicationUser类所以现在下面的代码对我来说是无用的,我想因为这个我的cookie过期了,我猜(我仍然不确定):
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
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))
}
});
}
注意:**保持会话活跃很长一段时间的原因是因为我的mvc应用程序是角驱动的Http get调用,Http post调用,所以当用户会话过期,如果我尝试任何**get或post调用,那么在会话过期的情况下什么都不会发生,但是当我刷新我的整个页面时,用户被重定向到登录页面,但直到那时用户将如何知道发生了什么,如果用户不刷新页面。
你的问题是缺少SecurityStamp
。安全戳是一个随机字符串,用于检查是否在服务器上更改了密码。安全戳记存储在cookie中,并不时地根据数据库进行检查。如果数据库(存储)中的值与cookie中的值不同,则要求用户登录。SecurityStampValidator
正在执行所有的检查和cookie无效。
你正在使用用户的自定义存储,这很好,但是你的存储不实现IUserSecurityStampStore
,当用户登录cookie没有得到SecurityStamp
的值。这将导致SecurityStampValidator
的故障。
所以你的选项是:
- 在你的商店中实现
IUserSecurityStampStore
。 - 从你的配置中删除
SecurityStampValidator
。
由于安全问题,我不喜欢第二种选择。您希望您的用户永远保持登录状态——这意味着cookie永远不会失效。但是当用户有两个浏览器时,两个都登录了。在其中一个浏览器中更改密码-第二个应该注销并要求密码。如果不检查安全戳,第二浏览器将不会注销,cookie仍然有效。现在想象一下,第二个浏览器在公共计算机上打开,用户忘记注销-没有办法结束会话,即使更改密码。
要实现IUserSecurityStampStore
,请查看合同:
/// <summary>
/// Stores a user's security stamp
/// </summary>
/// <typeparam name="TUser"></typeparam>
/// <typeparam name="TKey"></typeparam>
public interface IUserSecurityStampStore<TUser, in TKey> : IUserStore<TUser, TKey> where TUser : class, IUser<TKey>
{
/// <summary>
/// Set the security stamp for the user
/// </summary>
/// <param name="user"></param>
/// <param name="stamp"></param>
/// <returns></returns>
Task SetSecurityStampAsync(TUser user, string stamp);
/// <summary>
/// Get the user security stamp
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
Task<string> GetSecurityStampAsync(TUser user);
}
基本上,这将在用户表中添加另一列:SecurityStamp
,并且需要在其中保存一个字符串。戳记的值是任意随机字符串。默认标识实现(大约734行)使用Guid.NewGuid().ToString()
-我建议您也这样做。
你的用户存储看起来像这样:
public class UserStore : IUserStore<UserModel>, IUserPasswordStore<UserModel>, IUserSecurityStampStore<TUser>
{
// your other methods
public async Task SetSecurityStampAsync(TUser user, string stamp)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
user.SecurityStamp = stamp;
return Task.FromResult(0);
}
Task<string> GetSecurityStampAsync(TUser user)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
return Task.FromResult(user.SecurityStamp);
}
}
请注意-在此操作中不需要将用户保存到存储中。UserManager
在UpdateSecurityStampAsync
中为你做这个——除非你自己重写这个方法。
创建新用户时,不要忘记给SecurityStamp
字段赋值。并使用一个值更新所有现有用户。这样的东西将工作update MyUsersTable set SecurityStamp = convert(nvarchar(38), NewId())
我有同样的问题,我真的很困惑,因为没有任何原因用户被重定向到登录页面意味着他没有被授权。我将超时时间更改为8小时以上,但没有任何更改。在阅读了许多页面后,例如Aspnet意外登出或频繁意外的用户注销,我发现机器密钥有问题,并在web中检查了机器密钥。配置文件,我可以检测到机器密钥的问题。通过更改机器密钥并使其与Owin部分的其他密钥相同,一切都工作良好。
ExpireTimeSpan = TimeSpan.FromDays(7);
那么这将使你的代码:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
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))
}
});
ExpireTimeSpan = TimeSpan.FromDays(7);
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
}
}
您还应该在IIS的应用程序池级别配置会话超时,如下所述:https://technet.microsoft.com/en-us/library/cc771956(v=ws.10).aspx
在特定的时间间隔调用控制器方法,因此它将在每次调用时重置会话超时。例如,如果你最初设置会话超时为30分钟,在20分钟后调用此操作,它将重新设置会话超时为30分钟,通过这种方式,您的会话即使在登录后30分钟仍保持活跃。
放置JQuery代码在布局
JQuery:var RefreshSessionInterval;
$(document).ready(function () {
clearInterval(RefreshSessionInterval);
RefreshSessionInterval = setInterval("RefreshSession()", 30000); // change your interval time as per requirement
});
function RefreshSession() {
$.ajax({
type: "POST",
url: '@Url.Action("RefreshSession", "YourControllerName")',
success: function (data) {
},
error: function () {
}
});
}
控制器:
Public void RefreshSession()
{
//your session reset from this line, as i know you don't have to write any code here.
}
public bool LogOut()
{
LogOff();
return true;
}
void LogOut()
{
Session.Clear();
Session.Abandon();
Session.RemoveAll();
ClearCache();
}
void ClearCache()
{
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetExpires(DateTime.Now.AddSeconds(-1));
Response.Cache.SetNoStore();
////FormsAuthentication.SignOut();
}
当我编写一个用户保持登录时,我是这样做的…
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
}
}
<<p> 帐户控制器/strong> public class AccountController : Controller
{
/// <summary>
/// Initializes a new instance of the <see cref="AccountController"/> class.
/// </summary>
public AccountController()
: this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AccountController"/> class.
/// </summary>
/// <param name="userManager">The user manager.</param>
public AccountController(UserManager<ApplicationUser> userManager)
{
UserManager = userManager;
}
/// <summary>
/// Gets the user manager.
/// </summary>
/// <value>
/// The user manager.
/// </value>
public UserManager<ApplicationUser> UserManager { get; private set; }
//
// GET: /Account/Login
/// <summary>
/// Logins the specified return URL.
/// </summary>
/// <param name="returnUrl">The return URL.</param>
/// <returns></returns>
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
//
// POST: /Account/Login
/// <summary>
/// Logins the specified model.
/// </summary>
/// <param name="model">The model.</param>
/// <param name="returnUrl">The return URL.</param>
/// <returns></returns>
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindAsync(model.UserName, model.Password);
if (user != null)
{
await SignInAsync(user, model.RememberMe);
return RedirectToLocal(returnUrl);
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
或. .您还可以在IIS的应用程序池级别为用户配置会话超时。
会话生存期(多长时间直到会话消失)和身份验证生存期(多长时间直到用户必须再次登录)是两个独立且不同的时间框架。
如果认证生命周期大于会话时间,这意味着会话将在用户已经认证的情况下开始(即用户不需要登录来发起会话)。
如果身份验证生命周期短于会话时间框架,这意味着用户将在会话到期之前强制登录。我不确定当用户重新身份验证时会话是否"刷新"(作为猜测…可能).
仅仅为会话和身份验证设置很长的过期时间可能不是一个盗版生产准备的解决方案(即有很多方法可以让会话"消失")。
为什么你关心用户的会话消失,然后一个新的会话开始(不需要用户登录)?如果不了解你的意思,我就不能真正理解你问题的核心。
检查web的身份验证元素中的forms元素的设置。配置文件。
请注意两个适用设置的默认值。
- 超时时间(默认为30分钟)
- slidingExpiration (True或False/默认值随。net框架版本而异)
根据您的情况,您可能希望超时时间比30分钟高得多,并且slidingExpiration值为True。