ASP.NET Forms身份验证超时

时间:2012-10-15 18:56:58

标签: asp.net timeout forms-authentication

这可能是一个非常简单的问题,但在几个小时后试图理解它在ASP.NET 4.0上是如何工作的,我仍然不知道。

我正在使用表单身份验证。我有一个登录页面,上面有登录控件。

这是我在用户登录时所需要的:

A-用户应该保持记录,直到不对超时设置做任何事情。如果他们重新加载页面,则超时必须重新开始倒计时。

B-如果他们点击“记住我”,他们应该保持联系,直到他们退出,无论他们是关闭浏览器还是重新启动计算机。

我遇到的问题是他们登录时我的计算机上没有看到任何cookie:

  1. 饼干在哪里?是内存cookie吗?
  2. 如果会话过期会怎样?除非超时完成,否则我想让它们保持记录状态。
  3. 如果应用程序池被回收会怎样?
  4. 另外我还有另一个问题:当他们点击“记住我”检查(案例B)时,我希望他们记录,直到他们点击退出按钮。这次我确实看到了一个cookie,但看起来它们只是在超时时保持连接...那么记住我与否的区别是什么......

    我想完全分开身份验证和会话。我希望身份验证由cookie控制,如果接近不是很糟糕的话。

    感谢您的帮助 - 。

2 个答案:

答案 0 :(得分:28)

处理非永久性,滑动到期门票

表单身份验证使用内存中的cookie作为故障单,除非您使其持久化(例如,FormsAuthentication.SetAuthCookie(username, true)将使其持久)。默认情况下,票证使用滑动到期。每次处理请求时,票证都将以新的到期日期发送。该日期到期后,cookie和票证都无效,用户将被重定向到登录页面。

表单身份验证没有内置处理方法来重定向已经呈现的页面,其长度超过超时。您需要自己添加。在最简单的级别,您需要使用JavaScript启动文档加载的计时器。

<script type="text/javascript">
  var redirectTimeout = <%FormsAuthentication.Timeout.TotalMilliseconds%>
  var redirectTimeoutHandle = setTimeout(function() { window.location.href = '<%FormsAuthentication.LoginUrl%>'; }, redirectTimeout);
</script>

通过上述操作,如果您的页面未刷新或更改,或者redirectTimeoutHandle未被取消(使用clearTimeout(redirectTimeoutHandle);),则会将其重定向到登录页面。 FormsAuth票证应已经过期,因此您不必对此做任何事情。

这里的诀窍是您的网站是否支持AJAX,或者您将其他客户端事件视为活动用户活动(移动或单击鼠标等)。您必须手动跟踪这些事件,并在发生这些事件时重置redirectTimeoutHandle。例如,我有一个大量使用AJAX的站点,因此页面不会经常进行物理刷新。由于我使用jQuery,我可以让它在每次发出AJAX请求时重置超时,这实际上应该导致页面被重定向,如果它们位于单个页面上并且不进行任何更新。

这是一个完整的初始化脚本。

$(function() {
   var _redirectTimeout = 30*1000; // thirty minute timeout
   var _redirectUrl = '/Accounts/Login'; // login URL

   var _redirectHandle = null;

   function resetRedirect() {
       if (_redirectHandle) clearTimeout(_redirectHandle);
       _redirectHandle = setTimeout(function() { window.location.href = _redirectUrl; }, _redirectTimeout);
   }

   $.ajaxSetup({complete: function() { resetRedirect(); } }); // reset idle redirect when an AJAX request completes

   resetRedirect(); // start idle redirect timer initially.
});

通过简单地发送一个AJAX请求,客户端超时和票证(以cookie的形式)都将被更新,您的用户应该没问题。

但是,如果用户活动不会导致更新FormsAuth票证,则用户下次请求新页面时(通过导航或通过AJAX)似乎会被注销。在这种情况下,当用户活动发生时,您需要通过AJAX调用(例如,自定义处理程序,MVC操作等)来“ping”您的Web应用程序,以使您的FormsAuth票证保持最新。请注意,在ping服务器以保持最新时需要小心,因为您不希望服务器充满请求,例如,移动光标或单击事物。除了初始页面加载和AJAX请求之外,上面的init脚本还添加了resetRedirect鼠标点击文档。

$(function() {
   $(document).on('click', function() {
      $.ajax({url: '/ping.ashx', cache: false, type: 'GET' }); // because of the $.ajaxSetup above, this call should result in the FormsAuth ticket being updated, as well as the client redirect handle.
   });
});

处理“永久”门票

您需要将票证作为持久性cookie发送到客户端,并且具有任意长的超时。您应该可以保留客户端代码和web.config,但是在登录逻辑中单独处理用户对永久票证的偏好。在这里,您需要修改故障单。下面是登录页面中的逻辑来做这样的事情:

// assumes we have already successfully authenticated

if (rememberMe)
{
    var ticket = new FormsAuthenticationTicket(2, userName, DateTime.Now, DateTime.Now.AddYears(50), true,
                                               string.Empty, FormsAuthentication.FormsCookiePath);
    var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket))
                     {
                         Domain = FormsAuthentication.CookieDomain,
                         Expires = DateTime.Now.AddYears(50),
                         HttpOnly = true,
                         Secure = FormsAuthentication.RequireSSL,
                         Path = FormsAuthentication.FormsCookiePath
                     };
    Response.Cookies.Add(cookie);
    Response.Redirect(FormsAuthentication.GetRedirectUrl(userName, true));
}
else
{
    FormsAuthentication.RedirectFromLoginPage(userName, false);
}

奖励:在故障单中存储角色

您询问是否可以在故​​障单/ cookie中存储角色,因此您无需再次查找它们。是的,这是可能的,但有一些考虑因素。

  1. 您应该限制您在故障单中放入的数据量,因为Cookie只能这么大
  2. 您应该考虑是否应该在客户端缓存角色。
  3. 详细说明#2:

    您不应该隐含地信任您从用户那里收到的声明。例如,如果用户登录并且是管理员,并且检查“记住我”从而接收持久的长期票证,则他们将永远是管理员(或者直到该cookie过期或被删除)。如果有人在数据库中将其从该角色中删除,则应用程序仍然认为如果他们拥有旧票证,则他们是管理员。因此,您最好每次获取用户的角色,但在应用程序实例中缓存角色一段时间以最大限度地减少数据库工作。

    从技术上讲,这也是票证本身的问题。同样,您不应该相信这只是因为他们拥有该帐户仍然有效的有效票证。您可以使用与角色类似的逻辑:通过查询实际数据库检查故障单引用的用户是否仍然存在并且是否有效(它没有被锁定,禁用或删除),只需将数据库缓存缓存一段时间时间来提高绩效。这就是我在我的应用程序中所做的事情,其中​​票证被视为身份声明(类似地,用户名/密码是另一种类型的声明)。这是global.asax.cs(或HTTP模块)中的简化逻辑:

    protected void Application_AuthenticateRequest(Object sender, EventArgs e)
    {
      var application = (HttpApplication)sender;
      var context = application.Context;  
    
      EnsureContextUser(context);
    }
    
    private void EnsureContextUser(HttpContext context)
    {
       var unauthorizedUser = new GenericPrincipal(new GenericIdentity(string.Empty, string.Empty), new string[0]);
    
       var user = context.User;
    
       if (user != null && user.Identity.IsAuthenticated && user.Identity is FormsIdentity)
       {
          var ticket = ((FormsIdentity)user.Identity).Ticket;
    
          context.User = IsUserStillActive(context, ticket.Name) ? new GenericPrincipal(user.Identity, GetRolesForUser(context, ticket.Name)) : unauthorizedUser;
    
          return; 
       }
    
       context.User = unauthorizedUser;
    }
    
    private bool IsUserStillActive(HttpContext context, string username)
    {
       var cacheKey = "IsActiveFor" + username;
       var isActive = context.Cache[cacheKey] as bool?
    
       if (!isActive.HasValue)
       {
          // TODO: look up account status from database
          // isActive = ???
          context.Cache[cacheKey] = isActive;
       }
    
       return isActive.GetValueOrDefault();
    }
    
    private string[] GetRolesForUser(HttpContext context, string username)
    {
       var cacheKey = "RolesFor" + username;
       var roles = context.Cache[cacheKey] as string[];
    
       if (roles == null)
       {
          // TODO: lookup roles from database
          // roles = ???
          context.Cache[cacheKey] = roles;
       }
    
       return roles;
    }
    

    当然,您可能决定不关心其中的任何内容,只想信任该票证,并将角色也存储在票证中。首先,我们从上面更新您的登录逻辑:

    // assumes we have already successfully authenticated
    
    if (rememberMe)
    {
        var ticket = new FormsAuthenticationTicket(2, userName, DateTime.Now, DateTime.Now.AddYears(50), true, GetUserRolesString(), FormsAuthentication.FormsCookiePath);
        var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket))
                         {
                             Domain = FormsAuthentication.CookieDomain,
                             Expires = DateTime.Now.AddYears(50),
                             HttpOnly = true,
                             Secure = FormsAuthentication.RequireSSL,
                             Path = FormsAuthentication.FormsCookiePath
                         };
        Response.Cookies.Add(cookie);
        Response.Redirect(FormsAuthentication.GetRedirectUrl(userName, true));
    }
    else
    {
        var ticket = new FormsAuthenticationTicket(2, userName, DateTime.Now, DateTime.Now.AddMinutes(FormsAuthentication.Timeout), false, GetUserRolesString(), FormsAuthentication.FormsCookieName);
        var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket))
           {
              Domain = FormsAuthentication.CookieDomain,
              HttpOnly = true,
              Secure = FormsAuthentication.RequireSSL,
              Path = FormsAuthentication.FormsCookiePath
           };
        Response.Cookies.Add(cookie);
        Response.Redirect(FormsAuthentication.GetRedirectUrl(userName, false));
    }
    

    添加方法:

       private string GetUserRolesString(string userName)
       {
            // TODO: get roles from db and concatenate into string
       }
    

    更新你的global.asax.cs以获取故障单中的角色并更新HttpContext.User:

    protected void Application_AuthenticateRequest(Object sender, EventArgs e)
    {
      var application = (HttpApplication)sender;
      var context = application.Context;  
    
      if (context.User != null && context.User.Identity.IsAuthenticated && context.User.Identity is FormsIdentity)
      {
          var roles = ((FormsIdentity)context.User.Identity).Ticket.Data.Split(",");
    
          context.User = new GenericPrincipal(context.User.Identity, roles);
      }
    }
    

答案 1 :(得分:1)

对于 A ,您需要将会话超时变量设置为您希望用户保持登录The Timeout property specifies the time-out period assigned to the Session object for the application, in minutes. If the user does not refresh or request a page within the time-out period, the session ends.

的时间长度

对于 B 部分,我建议将该值存储在会话变量(或cookie但不驻留在服务器上)并在global.asax中的Session_End事件中检查该值文件。如果已设置,则续订会话。

当浏览器关闭时,Session_End事件不会触发,当服务器在特定时间段内没有收到用户的请求时(默认为20分钟),它会触发)。