该功能的引入目的很简单,防止人员离开工位时忘记登出系统,其他人员冒名操作,追责时产生争议。
首先,介绍下本文的参考资料:
0. Http请求处理流程
IIS 、Framework、Asp.Net 是如何协同工作处理每个Http请求、如何区分不同的请求、IIS、Framework、Asp.Net三者之间的数据如何流动?
能够处理各种后缀名的应用程序,通常被称为 ISAPI 应用程序(Internet Server Application Programe Interface,互联网服务器应用程序接口)
Asp.Net 只是服务器(IIS)的一个组成部分而已,它是一个 ISAPI扩展。
HttpRuntime类是Asp.Net的一个主要入口,它有一个称作 ProcessRequest 的方法,这个方法以一个 HttpWorkerRequest 类作为参数。HttpRuntime 类几乎包含着关于单个 Http请求的所有信息:
所请求的文件、服务器端变量、QueryString、Http 头信息 等等。
当 Web.config文件的内容发生改变 或者 .aspx文件发生变动的时候,为了能够卸载运行在同一个进程中的应用程序(卸载也是为了重新加载),Http请求被分放在相互隔离的应用程序域中。
对于IIS来说,它依赖一个叫做 HTTP.SYS 的内置驱动程序来监听来自外部的 HTTP请求。在操作系统启动的时候,IIS首先在HTTP.SYS中注册自己的虚拟路径。
当Http请求进入 Asp.Net Runtime以后,它的管道由托管模块(NOTE:Managed Modules)和处理程序(NOTE:Handlers)组成,并且由管道来处理这个 Http请求。
1. HttpModule介绍
一般来说,我们可以将Asp.Net中的事件分成三个级别,最顶层是 应用程序级事件、其次是页面级事件、最下面是控件级事件,事件的触发分别与 应用程序周期、页面周期、控件周期紧密相关。
而 Http Module 的作用是与应用程序事件 密切相关的。
Asp.Net 内置的 Http Modules
整个过程如下:
-
当站点第一个资源被访问的时候,Asp.Net会创建HttpApplication类的实例,它代表着站点应用程序,同时会创建所有在Web.Config中注册过的Module实例。
-
在创建Module实例的时候会调用Module的Init()方法。
-
在Init()方法内,对想要作出响应的HttpApplication暴露出的事件进行注册。(仅仅进行方法的简单注册,实际的方法需要另写)。
-
HttpApplication在其应用程序周期中触发各类事件。
-
触发事件的时候调用Module在其Init()方法中注册过的方法。
2. HttpModule模块作用之三通过Session判断用户是否登陆
3. 判断Session过期
上述两个参考文档是可以直接参考的代码,非原理性讲解。
4. 利用HttpModule开发asp.net页面、ashx等访问时session失效的统一处理入口
HttpApplication处理事件的先后顺序,不同事件会处理不同的事情,需要注意使用顺序。
5. ASP.NET三剑客 HttpApplication HttpModule HttpHandler 解析
详细介绍了上述博客提到的HttpApplication、HttpModule、HttpHandler等对象
上面介绍的方法都是通过HttpModule判断session是否失效来决定是否返回登陆页面。
那么什么是Session,怎么设定Session的存续事件,集群环境下Session如何处理?这些问题的答案可以参考6-8这3篇博客。
经过上面的参考文档,我们的代码如下:
public class ModuleBackLogin : IHttpModule { public ModuleBackLogin() { // // TODO: 在此处添加构造函数逻辑 // } // Init方法仅用于给期望的事件注册方法 public void Init(HttpApplication context) { context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute); } // 处理context_PreRequestHandlerExecute事件的实际代码 void context_PreRequestHandlerExecute(object sender, EventArgs e) { try { HttpApplication ha = (HttpApplication)sender;//获得发生该事件的对象 string path = ha.Context.Request.Url.ToString();//获得请求的url路径 int n = path.IndexOf("Login.aspx"); //判断是否是登陆页面 int m = path.IndexOf("LoginHandler.ashx"); //不是登录页面,也不是登录操作 if (n < 0 && m < 0) { if (ha.Context.Session != null) { if (ha.Context.Session[KeyConst.UserId] == null) { ha.Response.Write("<script>alert('登陆超时或者非法登录,请重新登录!!');top.window.location.href='Login.aspx';</script>"); ha.Response.End(); } } } } catch { } } public void Dispose() { } }
<?xml version="1.0" encoding="utf-8"?> <!-- 有关如何配置 ASP.NET 应用程序的详细信息,请访问 http://go.microsoft.com/fwlink/?LinkId=169433 --> <configuration> <system.web> <compilation debug="true" targetFramework="4.5" /> <httpRuntime targetFramework="4.5" requestValidationMode="2.0"/> <!--<httpModules> <add name="MyModule" type="ModuleBackLogin" /> </httpModules>--> <sessionState mode="InProc" timeout="10"/> <!--<httpRuntime requestValidationMode="2.0" />--> <pages validateRequest="false"></pages> </system.web> <system.webServer> <modules> <add name="MyModule" type="ModuleBackLogin" /> </modules> </system.webServer> </configuration>
上述的参考文档及代码是使用ASP.NET及Session判断网页过期及强制重新登录。
下一个项目我们使用的是ASP.NET MVC,那么这个要怎么处理呢?上述解决方案还有一个隐藏问题,如果请求页面是OK的,但是如果是AJAX请求,那么页面会报错,但是并不会强制重新登录,这个问题要怎么解决呢?请继续往下阅读~
参考资料如下:
1. Asp.net MVC使用Filter解除Session, Cookie等依赖
filter的简单介绍。
MVC对HTTP请求的处理和ASP.NET有些不一样,比如MVC是通过给Controller加Filter,ASP.NET是通过HttpModule。
该文章介绍了使用Filter进行session过期的判断。
也解决了前文我提到的一个问题,AJAX请求失败的问题。
但是文章还有个问题待解决,难道要在网页上所有的ajax请求处都要加以处理?
当授权过期后,针对ajax请求返回一个特定的标识,然后前端通过识别该标识,来跳转到登录页面。而且最好不用在每处ajax请求的代码中都加上对这种标识判断,即要能全局处理。
所以目标就落在了jQuery.ajax的全局配置($.ajaxSetup)上了,通过查看API,发现statusCode参数用来做这件事再好不过了,而且重要的是,即使ajax代码中禁用了全局配置(global:false),
关于statusCode的配置都仍然有效(这点对我们之前项目中来说很重要,因为有很多的ajax都禁用了全局的遮罩效果)。
如果不想在每个controller上都加特性,那么可以将filter注册成全局过滤器。
综上,MVC中我们的代码如下:
namespace MySpace { /// <summary> /// 判断是否登录的过滤器 /// </summary> public class JudgmentLoginAttribute : ActionFilterAttribute { //执行Action之前操作 public override void OnActionExecuting(ActionExecutingContext filterContext) { //判断是否登录或是否用权限,如果有那么就进行相应的操作,否则跳转到登录页或者授权页面 string s_accountId = HttpUtility.UrlDecode(CookiesHelper.GetCookieValue("UserID")); string s_loginTime = HttpUtility.UrlDecode(CookiesHelper.GetCookieValue("LoginTime")) == ""?"2000-01-01 00:00:00": HttpUtility.UrlDecode(CookiesHelper.GetCookieValue("LoginTime")); if((DateTime.Now -DateTime.Parse(s_loginTime)).Minutes >30)//超时30分钟则需要重新登录 { if (filterContext.HttpContext.Request.IsAjaxRequest()) { filterContext.Result = new HttpStatusCodeResult(499); filterContext.HttpContext.Response.Write("alert('登陆超时或者非法登录,请重新登录!');top.window.location.href='/Auth/Login';"); } else { HttpContext.Current.Response.Write("<script>alert('登陆超时或者非法登录,请重新登录!');top.window.location.href='/Auth/Login';</script>"); } } else { CookiesHelper.SetCookie("LoginTime", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); } } } }
[MySpace.JudgmentLogin] public class HomeController : Controller
$.ajaxSetup({ statusCode: { 499: function (data) { //console.log(data); eval(data.responseText); } } });