5

背景:

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

部分工作解决方案:

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

问题:

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

我不想做的事情

我不想浏览每一个页面、每一个 gridview、每一个 imagebutton 等并绑定一些客户端事件来跟踪用户操作。如果可能的话,我想要更多……通用且可维护的解决方案。

我的点子:

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

4

5 回答 5

0

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

于 2012-11-05T21:56:41.790 回答
0

实现 IHttpModule,附加到适当的 HttpApplication 事件,并在其中做你想做的事。

于 2012-11-14T16:54:43.810 回答
0

尽管它不能完全满足您的要求,但也许这个Timeout 控件至少可以提供一些灵感?

*更新:博客上有一些评论来自一个实现滑动到期控件的人以及他如何使其工作。

于 2012-11-15T19:25:43.100 回答
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 存在问题,使用了唯一类)并将其文本属性设置为计算值 $(obj).text(calculatedValue)

为了结束

请确保,当您读取 cookie 以将它们解析为浮点数时,以确保这一点。我有兴趣看到在这种特殊情况下服务器推送技术的应用和 Web 套接字的使用,但我认为如果您要跟踪和计算服务器上每个客户端的会话过期时间,那将是一种矫枉过正的做法。我知道我已经要求在服务器上进行会话过期跟踪,这看起来像是在客户端上进行跟踪,但事实并非如此......这些 cookie 是我们跟踪所需要的,它们来自服务器,所以......就是这样.

于 2012-11-15T21:06:08.890 回答
0

这是我的解决方案。

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

  2. 向 Session 对象添加新键并将其填充到每个页面请求中(母版页的事件,如 Page_Init、Page_Load 等),包括回发和回调。

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

  3. 创建一个 HttpHandler,它实现了 IReadOnlySessionState 接口。

    public class SessionStateHandler : IHttpHandler, IReadOnlySessionState

将 SessionStateHandler 处理程序放入允许匿名访问的文件夹中(或将其放入根文件夹)。

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 设置为 30 分钟,则带有票证的新 cookie 将在第一次请求时发送给客户端以响应自会话开始 15 分钟后。因此,只要您想知道会话到期时间,而不是立即扩展它,那么您需要删除新的 ASP.NET 身份验证。来自响应的 cookie(查看 line 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 函数添加到母版页上的启动脚本来在页面加载时运行 JS 函数。

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

于 2019-12-17T11:54:06.693 回答