我们有一个使用本机表单身份验证和会话功能的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;
}
}
客户端每10秒调用一次此Web Api服务,以检查身份验证或会话是否已过期。如果是,则脚本将浏览器重定向到登录页面。这就像一个魅力。
但是,调用此服务会触发身份验证和会话的滑动过期。因此,基本上,创建永无止境的身份验证和会话。我在服务的开头设置了一个断点,以检查它是否是我们自己的一个触发它的函数。但事实并非如此,在执行服务之前,它似乎发生在ASP.NET的更深处。
答案 0 :(得分:13)
这似乎是不可能的。启用滑动到期后,始终会触发它。如果有办法访问会话而不扩展会话,我们无法找到它。
那么如何解决这个问题呢?我们提出了以下替代解决方案,以解决最初在问题中提出的解决方案。这个实际上更有效率,因为它不会每隔x秒使用Web服务回家。
因此,我们希望能够知道ASP.NET的表单身份验证或会话何时过期,因此我们可以主动注销用户。每个页面上都有一个简单的javascript计时器(Khalid Abuhakmeh as proposed)是不够的,因为用户可能同时在多个浏览器窗口/标签中使用该应用程序。
我们为使此问题更简单而做出的第一个决定是使会话的到期时间比表单身份验证的到期时间长几分钟。这样,会话将永远在表单身份验证之前到期。如果下次用户尝试登录时存在延迟的旧会话,我们会放弃它以强制使用新的会话。
好的,现在我们只需要考虑表单身份验证到期。
接下来,我们决定禁用表单身份验证的自动滑动过期(在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);
}
}
我们从应用程序的BasePage类中的OnPreRenderComplete
事件中调用此方法,其他每个页面都从该类继承。它与正常的滑动过期功能完全相同,但我们有机会做一些额外的事情;比如调用我们的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);
}
现在我们有一个额外的cookie,即使用户在不同的浏览器窗口/标签中工作,也始终代表正确的表单身份验证到期时间。毕竟,cookie具有浏览器范围。现在唯一剩下的是用于验证cookie值的javascript函数。
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来检索Cookie。)
将此功能放在一个间隔中,用户将在其表单身份验证过期时注销。 Voilà:-)这个实现的额外好处是,您现在可以控制表单身份验证的到期何时扩展。如果您想要一堆不延长过期时间的Web服务,请不要为它们调用RenewAuthenticationTicket
方法。
如果您有任何要添加的内容,请发表评论!
答案 1 :(得分:1)
这一切都可以在客户端解决,无需返回服务器。
在JavaScript中执行此操作。
var timeout = setTimeout(function () {
window.location = "/login";
}, twentyMinutesInMilliseconds + 1);
每次刷新页面时,超时时间设置为20分钟。这可确保用户需要在超时发生之前完成所有工作。很多站点都使用这种方法,它可以避免你做不必要的服务器请求。
答案 2 :(得分:1)
您的网站功能应该在没有JavaScript的情况下运行,或者您只需将一个问题替换为另一个我也解决了这个问题,以下是解决方法:
当您对自己进行身份验证时,将创建会话cookie,默认生命周期为20分钟。当此过期时,用户将被注销。
当用户选择&#34;记住我&#34;在登录表单中,然后在客户端和数据库中创建其他持久性cookie [AuthCookie]。这个cookie的生命周期为1个月。每当加载页面时,会使用新生命周期重新创建会话和持久性cookie数据(通常您希望解密/加密票证)。
想象一下以下场景。用户已在我们的应用程序中工作 暂时然后继续做其他事情,离开我们 应用程序闲置20分钟。然后用户返回我们的 申请撰写报告。当用户尝试保存时,会话将在请求之前恢复。
执行此操作的一种方法是扩展global.aspx以处理预先请求。有些东西:
void application_PreRequestHandlerExecute(object sender, EventArgs e){
...
if (HttpContext.Current.Handler is IRequiresSessionState) {
if (!context.User.Identity.IsAuthenticated)
AuthService.DefaultProvider.AuthenticateUserFromExternalSource();
AuthenticateUserFromExternalSource应检查cookie数据是否与数据库数据匹配,因为可以更改存储在客户端的任何内容。如果您具有访问权限的付费服务,则需要检查用户是否仍具有这些权限,然后您可以重新创建会话。