关于ASP.NET表单身份验证和会话的滑动到期

maa*_*ker 16 c# asp.net session webforms asp.net-web-api

我们有一个使用本机表单身份验证和会话功能的ASP.NET 4.5 WebForms应用程序.滑动到期时两者都超时20分钟.

想象一下以下场景.用户已经在我们的应用程序中工作了一段时间,然后继续做其他事情,让我们的应用程序闲置20分钟.然后,用户返回到我们的应用程序以编写报告.但是,当用户尝试保存时,他/她将使用登录屏幕进行处理,并且报告将丢失.

显然,这是不需要的.我们希望在身份验证或会话过期时将浏览器重定向到登录页面,而不是这种情况.为了实现这一点,我们构建了一个Web Api服务,可以调用它来检查是否是这种情况.

public class SessionIsActiveController : ApiController
{
    /// <summary>
    /// Gets a value defining whether the session that belongs with the current HTTP request is still active or not.
    /// </summary>
    /// <returns>True if the session, that belongs with the current HTTP request, is still active; false, otherwise./returns>
    public bool GetSessionIsActive()
    {
        CookieHeaderValue cookies = Request.Headers.GetCookies().FirstOrDefault();
        if (cookies != null && cookies["authTicket"] != null && !string.IsNullOrEmpty(cookies["authTicket"].Value) && cookies["sessionId"] != null && !string.IsNullOrEmpty(cookies["sessionId"].Value))
        {
            var authenticationTicket = FormsAuthentication.Decrypt(cookies["authTicket"].Value);
            if (authenticationTicket.Expired) return false;
            using (var asdc = new ASPStateDataContext()) // LINQ2SQL connection to the database where our session objects are stored
            {
                var expirationDate = SessionManager.FetchSessionExpirationDate(cookies["sessionId"].Value + ApplicationIdInHex, asdc);
                if (expirationDate == null || DateTime.Now.ToUniversalTime() > expirationDate.Value) return false;
            }
            return true;
        }
        return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

客户端每10秒调用此Web Api服务,以检查身份验证或会话是否已过期.如果是,则脚本将浏览器重定向到登录页面.这就像一个魅力.

但是,调用此服务会触发身份验证和会话的滑动到期.因此,基本上,创建永无止境的身份验证和会话.我在服务的开头设置了一个断点,以检查它是否是我们自己的一个触发它的函数.但事实并非如此,它似乎发生在ASP.NET的更深处,在服务执行之前.

  1. 有没有办法禁用ASP.NET的身份验证和会话滑动到期特定请求的触发?
  2. 如果没有,那么解决这样的场景的最佳做法是什么?

maa*_*ker 13

  1. 这似乎是不可能的.启用滑动到期后,将始终触发它.如果有一种方法可以访问会话而不扩展它,我们无法找到它.

  2. 那么如何解决这个问题呢?我们提出了以下替代解决方案,以解决最初在问题中提出的解决方案.这个实际上更有效率,因为它不会每隔x秒使用Web服务回家.

因此,我们希望有一种方法可以知道ASP.NET的表单身份验证或会话何时过期,因此我们可以主动注销用户.每个页面上的一个简单的javascript计时器(由Khalid Abuhakmeh 提出)是不够的,因为用户可能同时在多个浏览器窗口/标签中使用该应用程序.

我们为使此问题更简单而做出的第一个决定是使会话的到期时间比表单身份验证的到期时间长几分钟.这样,会话将永远在表单身份验证之前到期.如果下次用户尝试登录时存在挥之不去的旧会话,我们会放弃它以强制刷新新会话.

好的,现在我们只需考虑表单身份验证到期.

接下来,我们决定禁用表单身份验证的自动滑动过期(在web.config中设置)并创建我们自己的版本.

public static void RenewAuthenticationTicket(HttpContext currentContext)
{
    var authenticationTicketCookie = currentContext.Request.Cookies["AuthTicketNameHere"];
    var oldAuthTicket = FormsAuthentication.Decrypt(authenticationTicketCookie.Value);
    var newAuthTicket = oldAuthTicket;
    newAuthTicket = FormsAuthentication.RenewTicketIfOld(oldAuthTicket); //This triggers the regular sliding expiration functionality.
    if (newAuthTicket != oldAuthTicket)
    {
        //Add the renewed authentication ticket cookie to the response.
        authenticationTicketCookie.Value = FormsAuthentication.Encrypt(newAuthTicket);
        authenticationTicketCookie.Domain = FormsAuthentication.CookieDomain;
        authenticationTicketCookie.Path = FormsAuthentication.FormsCookiePath;
        authenticationTicketCookie.HttpOnly = true;
        authenticationTicketCookie.Secure = FormsAuthentication.RequireSSL;
        currentContext.Response.Cookies.Add(authenticationTicketCookie);
        //Here we have the opportunity to do some extra stuff.
        SetAuthenticationExpirationTicket(currentContext);
    }
}
Run Code Online (Sandbox Code Playgroud)

我们从OnPreRenderComplete应用程序的BasePage类中的事件中调用此方法,其他每个页面都从该类继承.它与正常的滑动过期功能完全相同,但我们有机会做一些额外的事情; 比如调用我们的SetAuthenticationExpirationTicket方法.

public static void SetAuthenticationExpirationTicket(HttpContext currentContext)
{
    //Take the current time, in UTC, and add the forms authentication timeout (plus one second for some elbow room ;-)
    var expirationDateTimeInUtc = DateTime.UtcNow.AddMinutes(FormsAuthentication.Timeout.TotalMinutes).AddSeconds(1);
    var authenticationExpirationTicketCookie = new HttpCookie("AuthenticationExpirationTicket");
    //The value of the cookie will be the expiration date formatted as milliseconds since 01.01.1970.
    authenticationExpirationTicketCookie.Value = expirationDateTimeInUtc.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds.ToString("F0");
    authenticationExpirationTicketCookie.HttpOnly = false; //This is important, otherwise we cannot retrieve this cookie in javascript.
    authenticationExpirationTicketCookie.Secure = FormsAuthentication.RequireSSL;
    currentContext.Response.Cookies.Add(authenticationExpirationTicketCookie);
}
Run Code Online (Sandbox Code Playgroud)

现在我们有一个额外的cookie,即使用户在不同的浏览器窗口/标签中工作,也始终代表正确的表单身份验证到期时间.毕竟,cookie具有浏览器范围.现在唯一剩下的是一个javascript函数来验证cookie的值.

function CheckAuthenticationExpiration() {
    var c = $.cookie("AuthenticationExpirationTicket");
    if (c != null && c != "" && !isNaN(c)) {
        var now = new Date();
        var ms = parseInt(c, 10);
        var expiration = new Date().setTime(ms);
        if (now > expiration) location.reload(true);
    }
}
Run Code Online (Sandbox Code Playgroud)

(请注意,我们使用jQuery Cookie插件来检索cookie.)

将此功能放在一个间隔中,用户将在其表单身份验证过期时注销.Voilà:-)这个实现的额外好处是你现在可以控制表单身份验证的到期何时扩展.如果您想要一堆不延长过期时间的Web服务,请不要RenewAuthenticationTicket为它们调用方法.

如果您有任何要添加的内容,请发表评论!