跟踪会话和表单身份验证在服务器上保留到期时间

时间:2012-11-04 11:48:09

标签: c# asp.net session

背景

回到工作中,我正在开发一个Web窗体应用程序,它使用Session和FormsAuthentication超时的默认,滑动过期。我的任务是跟踪用户不活动状态,并在需要时显示模式窗口,其中包含有关会话到期的警告,计时器和刷新会话超时的按钮(它还会刷新表单身份验证超时)。按钮连接到主页面上具有空体的事件,它现在可以完成工作。 (我知道其他解决方案)。

部分工作解决方案:

我的方法是使用母版页 Page_PreRender 方法获取Session和FormsAuthentication超时值,比较它们并将较小(或任何)值传递给客户端的浏览器(带有其他必要数据)。在浏览器中,我会在模态窗口的某个特定时刻显示带有计时器的警告,并且它将按预期工作......即...当用户什么都不做时。

问题:

我们的应用程序有很多页面都有更新面板。这些更新面板用于与Session中存储的数据进行交互和操作。每当用户在其中一个更新面板中执行某些操作时,会话超时就会滑动(重新启动)。 我无法跟踪客户端上的内容(在现实世界中),我需要一种方法来跟踪服务器上的会话过期。

我不想做的事

我不想要浏览每一个页面,每个网格视图,每个图像按钮等,并绑定一些客户端事件以跟踪用户操作。如果可能的话,我想要更多......通用和可维护的解决方案。

我的想法:

我希望能够在到期之前跟踪/检查Session或FormsAuthentication剩余时间,或者在这些值已被重置时得到通知。这样我可以在客户端上获得关于我应该在显示模态窗口时发出警告的时刻的初始数据,那时我希望能够首先检查服务器 IF 该时间是正确的(大约)或者我是否应该在客户端上更新我的javascript数据并在显示警告窗口之前延长时间,因为会话到期已经更新... 那么拦截和过滤用户请求呢?那会有帮助吗?

5 个答案:

答案 0 :(得分:0)

您是否通过SQL Server考虑了会话状态?它允许您通过数据库管理会话,打开选项以查询所需的过期信息。看看这个link。或者link

答案 1 :(得分:0)

实现IHttpModule,附加到适当的HttpApplication事件并在其中执行您想要的操作。

答案 2 :(得分:0)

虽然它没有完全符合您的要求,但也许这个Timeout control可能至少会提供一些灵感?

*更新:来自实施控件的人blog对滑动过期以及如何使其工作有一些评论。

答案 3 :(得分:0)

首先,谢谢大家的建议。我上周成功地解决了这个问题,只是没有时间提前发布......坦白说,我对其他解决方案很感兴趣。我会确保阅读您发布和建议的所有材料。谢谢你。

这就是我的所作所为:

在服务器端:

我使用Cookie来跟踪会话到期时间。我想到的一件事是,即使在异步请求中,Page也会经历整个生命周期。整个想法是使用Master Page的Pre_Render事件在每个请求上设置适当的cookie。

我使用的是UTC时间格式,正在计算可翻译为javascript时间格式的毫秒数:

 var tmpDate = new DateTime(1970, 1, 1);
 var nowUtc = DateTime.UtcNow;
 var tmpSpan = new TimeSpan(nowUtc.Ticks - tmpDate.Ticks);
 return tmpSpan.TotalMilliseconds;

有了这个,我得到了我计算所需的一切,当前时间,会话到期时间(Session.Timeout)时间,以及警告的时间......从那里开始的基本数学。

在客户端:

首先,我重新启动所有三个cookie以开始跟踪并初始化两个单独的变量以跟踪当前服务器时间。它们都被初始化为当前服务器时间的第一个cookie读取的相同值。 然后,我使用 setTimeout 函数每秒读取cookie(当前服务器时间)(jQuery插件)。我正在检查以前 cookie读取的当前服务器时间和最新 cookie读取的当前服务器时间是否相等(单独的变量) - 这样我知道是否有任何请求如果有的话,我会读取其他2个cookie用于警告时间和超时时间以更新内容并关闭警告对话框(如果已打开)。之后,我将使当前服务器时间变量相等,并在每次读取cookie后继续比较它们,并仅更新其中一个。 此外,在每次setTimeout调用时,我都会使用

获取当前时间
new Date().getTime();

返回客户端计算机上测量的当前UTC时间。在任何情况下,我们都依赖于客户在他/她的机器上设置有效时钟的假设。之后我将当前时间与警告时间进行比较,如果当前时间比警告时间“更大”并且“小于”超时时间,那么我们必须显示弹出窗口。 如果当前时间比超时时间“更大”,请单击隐藏按钮,该按钮调用注销用户后面的母版页代码中的方法。即使用户在浏览器中打开了多个选项卡,这也很有效。一旦用户在一个选项卡中注销,每个尝试将其注销的其他页面(选项卡)都会被重定向到登录页面。 如果用户单击“确定”按钮确认他/她的存在,则触发后面的主页面代码中的空事件(单击隐藏的asp按钮)。这足以刷新会话。这将自动重新发送cookie,一切都按预期进行。 (假设你有滑动会话)。 在警告窗口中很容易实现计时器。我动态创建了带有消息和3个跨度的模态窗口的div。在每次setTimeout迭代时,我都设置了这样的计时器:

  1. 从当前javascript时间计算小时数,分钟数,秒数
  2. 将结果放在变量
  3. 如果结果小于10,则前置0(零)
  4. 按类定位适当的跨度(在IE 8中出现ID问题,使用了唯一的类)并将其text属性设置为计算值$(obj).text(calculatedValue)
  5. 结束

    确保当您正在阅读Cookie以将其解析为浮点数时,确保这一点。 我有兴趣在这种特殊情况下看到服务器推送技术的应用和网络套接字的使用,但我认为如果你在服务器上为每个客户端跟踪和计算会话过期,那将是一种过度杀伤力。 我知道我已经要求在服务器上进行会话过期跟踪,这看起来像在客户端上进行跟踪,但它不是......那些我们需要跟踪的cookie,它们来自服务器所以...就是这样

答案 4 :(得分:0)

这是我的解决方法。

  1. 确保将 FormsAuthentication.CookieMode 设置为使用cookie(默认情况下)。

  2. 向Session对象添加新键,并在每个页面请求(主页面的事件,例如Page_Init,Page_Load等)中填充它,包括回发和回调。

    Session["SessionUserLastPageRequestTime"] = MyApp.CommonUtils.GetServerDateTime();

  3. 创建一个实现IReadOnlySessionState接口的HttpHandler。

    public class SessionStateHandler : IHttpHandler, IReadOnlySessionState

将SessionStateHandler haldler放入具有允许匿名访问权限的文件夹中(或将其放入根文件夹中)。

public void ProcessRequest(HttpContext context)
{
    string requestData, alertMessage;
    using (var reader = new StreamReader(context.Request.InputStream))
    {
        requestData = reader.ReadToEnd();
    }
    /* -1: unauthorized;
     * -2: authorized, but no information about last request time;
     * -3: more than half session timeout, or not needed to inform user about session expiration;
     * >=0: lesser than half session timeout or if needed to inform user. The number reflects the value of total amount of minutes to session expiration. 
     */
    int status = -1, idleTimeInMinutes = 0;
    if (context.User?.Identity == null
        || !context.User.Identity.IsAuthenticated
        || context.Session == null
        || context.Session["SessionUserLastPageRequestTime"] == null)
    {
        status = -1;
        alertMessage = "Session expired!";
    }
    else
    {
        DateTime now = MyApp.CommonUtils.GetServerDateTime();
        var userLastPageRequest = (DateTime?)context.Session["SessionUserLastPageRequestTime"];
        if (userLastPageRequest == null)
        {
            status = -2;
            alertMessage = null;
        }
        else
        {
            var halfTimeout = TimeSpan.FromMinutes(context.Session.Timeout / 2);
            TimeSpan idleTime = now.Subtract(userLastPageRequest.Value);
            idleTimeInMinutes = (int)idleTime.TotalMinutes;
            if (idleTime <= halfTimeout)
            {
                status = -3;
                alertMessage = null;
            }
            else
            {
                status = context.Session.Timeout - idleTimeInMinutes;
                /* Send time to session expiration only if 3 or lesser minutes remain */
                if (status > 3)
                    status = -3;
                /* If session expiration time's expanding (sliding expiration) has been done in some way */
                else if (status < 0)
                    status = 0;
                alertMessage = string.Format("Session Will Expire In {0} Minutes", status);
            }
        }
    }
    context.Response.Write(JsonConvert.SerializeObject(new { Status = status, Message = alertMessage }));
    /* Remove new cookie to prevent sliding of session expiration time */
    if (requestData == "TIMEOUT_REQUEST")
        context.Response.Cookies.Remove(FormsAuthentication.FormsCookieName); /* by fact FormsAuthentication.FormsCookieName equals to ".ASPXAUTH" */
}

请注意,只有在自会话启动起至少有一半的会话超时时间消失后,服务器才检测到会话过期。如果Session.Timeout设置为30min,则自会话开始15分钟后,带有票证的新cookie将在响应第一个请求时发送给客户端。 因此,只要您想知道会话的到期时间,而不是立即延长会话到期时间,就需要删除新的ASP.NET身份验证。来自响应的Cookie(请查看第context.Response.Cookies.Remove(FormsAuthentication.FormsCookieName);行)。

  1. 使用setInterval或setTimer从客户端创建连续请求

var stc_ContiniousRequestOfSessionState_IntervalId;
    
function stc_ContiniousRequestOfSessionState() {
	stc_ContiniousRequestOfSessionState_IntervalId = setInterval(stc_ContiniousRequestOfSessionState_OnInterval, 20000);
}
function stc_ContiniousRequestOfSessionState_OnInterval() {
	stc_RequestSessionStateFromHttpHandler("TIMEOUT_REQUEST");
}
function stc_RequestSessionStateFromHttpHandler(dataToSend) {
	var url = '<%: Page.ResolveClientUrl("~/handlers/SessionStateHandler.ashx") %>';
	$.ajax({
		type: "POST",
		url: url,
		contentType: "application/json",
		dataType: "json",
		data: dataToSend,
		success: function (result) {
			stc_sessionState = result.Status;
			if (stc_sessionState == -2 || stc_sessionState == -3) {
				hideSessionEndWarningMessage();
			}
			else if (stc_sessionState == -1) {
				sessionExpired = true;
				clearInterval(stc_ContiniousRequestOfSessionState_IntervalId);
				showSessionTimeoutWarningMessage(result.Message);
			}
			else if (stc_sessionState >= 0) {
				showSessionTimeoutWarningMessage(result.Message);
			}
		},
		error: function (result) {
			/* we can increase request frequency, re-send session request instantly  or do something else at this place */
		}
	});
}

  1. 在页面加载时运行JS函数,方法是将其添加到母版页的启动脚本中。

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        if (Page.User.Identity.IsAuthenticated)
        {
            ScriptManager.RegisterStartupScript(this, this.GetType(), "test1", "continiousRequestOfSessionState();", true);
        }
    }
}