我们使用原生形式的认证和会话功能的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;
}
}
此Web API服务是由客户端每隔10秒调用以校验如果任一身份验证或会话已过期。 如果是这样,该脚本将浏览器重定向到登录页面。 这就像一个魅力。
然而,在调用这个服务触发了认证和会话的滑动过期。 因此,从本质上讲,打造永不落幕的认证和会话。 我已经在服务的开始设置一个断点,以检查它是否是我们自己的函数触发此一个。 但是,这是不是这样的,它似乎在某处ASP.NET更深发生时,服务的执行之前。
- 有没有一种方法来禁用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.
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.
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.
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);
}
}
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);
}
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);
}
}
(Note that we use jQuery Cookie Plugin to retrieve the cookie.)
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!
这些都可以解决客户端,而不需要返回到服务器。
在JavaScript中做到这一点。
var timeout = setTimeout(function () {
window.location = "/login";
}, twentyMinutesInMilliseconds + 1);
超时将被设置为每个页面刷新20分钟。 这确保了用户所需要的所有超时发生之前完成他们的工作。 很多网站都使用这种方法,它从做不必要的服务器请求,为您节省。
您的网站功能应该没有JavaScript的工作,或者你只是替换另一个问题。 我也解决了这个问题,这里是它是如何解决:
当你验证自己的身份,然后会话cookie默认生命周期的20分钟创建。 当该用户到期将被注销。
当用户选择“记住我”中然后加入另外的持久性cookie的[AuthCookie]在客户端被创建在形式的符号和在数据库中。 此cookie有1个月了一生。 每当加载页面时,会话和持久性cookie数据被重新以新的生命周期(通常要解密/隐窝票)。
想象一下以下情形。 用户在我们一会儿应用程序的工作,然后继续做一些其他的事情,让我们的应用闲置20分钟。 然后,用户返回到我们的应用程序写一份报告。 当用户试图保存,他的会话请求之前恢复。
要做到这一点的方法之一是扩大global.aspx处理prerequest。 东西的线路:
void application_PreRequestHandlerExecute(object sender, EventArgs e){
...
if (HttpContext.Current.Handler is IRequiresSessionState) {
if (!context.User.Identity.IsAuthenticated)
AuthService.DefaultProvider.AuthenticateUserFromExternalSource();
AuthenticateUserFromExternalSource应该检查一下cookie数据与数据库中的一个相匹配,因为存储在客户端什么都可以改变。 如果您有访问权限的有偿服务,那么你需要检查,如果用户仍然有这些权利,那么你可以重新创建会话。
文章来源: Concerning the sliding expiration of ASP.NET's forms authentication and session