问题描述
我们有一个使用本机表单身份验证和会话功能的 ASP.NET 4.5 WebForms 应用程序.两者都有 20 分钟的超时时间和滑动到期时间.
We have a ASP.NET 4.5 WebForms application using the native forms authentication and session functionality. Both have a timeout of 20 minutes with sliding expiration.
想象一下以下场景.一个用户在我们的应用程序中工作了一段时间,然后继续做一些其他的事情,让我们的应用程序空闲 20 分钟.然后用户返回到我们的应用程序来编写报告.但是,当用户尝试保存时,他/她会被登录屏幕处理,并且报告丢失.
Imagine the following scenario. A user has worked in our application for a while and then proceeds to do some other things, leaving our application idle for 20 minutes. The user then returns to our application to write a report. However, when the user tries to save, he/she is treated with the login screen, and the report is lost.
显然,这是不需要的.而不是这种情况,我们希望浏览器在身份验证或会话过期时重定向到登录页面.为了实现这一点,我们构建了一个 Web Api 服务,可以调用它来检查是否是这种情况.
Obviously, this is unwanted. Instead of this scenario, we want the browser to be redirected to the login page the moment either authentication or session has expired. To realize this, we have build a Web Api service that can be called to check whether this is the case.
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;
}
}
客户端每 10 秒调用一次此 Web Api 服务,以检查身份验证或会话是否已过期.如果是,脚本会将浏览器重定向到登录页面.这就像一个魅力.
This Web Api service is called every 10 seconds by the client to check if either authentication or session has expired. If so, the script redirects the browser to the login page. This works like a charm.
但是,调用此服务会触发身份验证和会话的滑动过期.因此,本质上,创建永无止境的身份验证和会话.我在服务开始时设置了一个断点,以检查是否是我们自己的函数之一触发了它.但事实并非如此,它似乎发生在 ASP.NET 更深的某个地方,在服务执行之前.
However, calling this service triggers the sliding expiration of both authentication and session. Thus, essentially, creating never ending authentication and session. I have set a breakpoint at the start of the service to check if it is one of our own functions that triggers this. But this is not the case, it seems to occur somewhere deeper in ASP.NET, before the execution of the service.
- 有没有办法针对特定请求禁用 ASP.NET 的身份验证和会话滑动到期的触发?
- 如果不是,那么处理此类场景的最佳做法是什么?
推荐答案
这似乎是不可能的.一旦启用滑动到期,它总是被触发.如果有一种无需扩展即可访问会话的方法,我们还没有找到.
This seems to be impossible. Once sliding expiration is enabled, it is always triggered. If there is a way to access the session without extending it, we have not been able to find it.
那么如何应对这种情况呢?我们针对问题中最初提出的解决方案提出了以下替代解决方案.这个实际上更有效,因为它不使用网络服务每 x 秒给家里打电话.
So how to tackle this scenario? We came up with the following alternative solution to the one originally proposed in the question. This one is actually more efficient because it doesn't use a web service to phone home every x seconds.
所以我们希望有一种方法可以知道 ASP.NET 的表单身份验证或会话何时过期,以便我们可以主动注销用户.每个页面上的简单 javascript 计时器(由 Khalid Abuhakmeh 提出)是不够的,因为用户可能正在使用应用程序同时在多个浏览器窗口/标签中.
So we want to have a way to know when either ASP.NET's forms authentication or session has expired, so we can pro-actively logout the user. A simple javascript timer on every page (as proposed by Khalid Abuhakmeh) would not suffice because the user could be working with the application in multiple browser windows/tabs at the same time.
为了简化这个问题,我们做出的第一个决定是使会话的过期时间比表单身份验证的过期时间长几分钟.这样,会话将永远不会在表单身份验证之前过期.如果用户下次尝试登录时仍有旧会话挥之不去,我们将放弃它以强制重新创建一个新会话.
The first decision we made to make this problem simpler is to make the expiration time of the session a few minutes longer than the expiration time of the forms authentication. This way, the session will never expire before the forms authentication. If there is a lingering old session the next time the user tries to log in, we abandon it to force a fresh new one.
好的,现在我们只需要将表单身份验证过期考虑在内.
All right, so now we only have to take the forms authentication expiration into account.
接下来,我们决定禁用表单身份验证的自动滑动过期(在 web.config 中设置)并创建我们自己的版本.
Next, we decided to disable the forms authentication's automatic sliding expiration (as set in the web.config) and create our own version of it.
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);
}
}
我们从应用程序的 BasePage 类中的 OnPreRenderComplete
事件调用此方法,其他所有页面都从该类继承.它与正常的滑动过期功能完全相同,但我们有机会做一些额外的事情;比如调用我们的 SetAuthenticationExpirationTicket
方法.
We call this method from the OnPreRenderComplete
event in our application's BasePage class, from which every other page inherits. It does exactly the same thing as the normal sliding expiration functionality, but we get the opportunity to do some extra stuff; like call our SetAuthenticationExpirationTicket
method.
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);
}
现在我们有一个额外的 cookie 可供我们使用,它始终代表正确的表单身份验证过期时间,即使用户在不同的浏览器窗口/选项卡中工作.毕竟,cookies 具有广泛的浏览器范围.现在唯一剩下的是一个 javascript 函数来验证 cookie 的值.
Now we have an extra cookie at our disposal that always represents the correct forms authentication expiration time, even if the user works in different browser windows/tabs. After all, cookies have a browser wide scope. Now the only thing left is a javascript function to verify the cookie's value.
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);
}
}
(注意我们使用jQuery Cookie Plugin来检索饼干.)
(Note that we use jQuery Cookie Plugin to retrieve the cookie.)
把这个函数放在一个时间间隔内,当他或她的表单身份验证过期时,用户将被注销.Voilà :-) 此实现的一个额外好处是您现在可以控制表单身份验证的到期时间何时延长.如果您想要一堆不延长到期时间的 Web 服务,请不要为它们调用 RenewAuthenticationTicket
方法.
Put this function in an interval, and users will be logged out the moment his or her forms authentication has expired. Voilà :-) An extra perk of this implementation is that you now have control over when the forms authentication's expiration gets extended. If you want a bunch of web services that don't extend the expiration, just don't call the RenewAuthenticationTicket
method for them.
如果你有什么要补充的,请留下评论!
Please drop a comment if you have anything to add!
这篇关于关于 ASP.NET 的表单身份验证和会话的滑动过期的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!