本文由中国C#技术学习中心整理 如果你对本文有不明之处请到技术论坛讨论!
一套.net窗体身份验证方案( 解决了防止用户重复登陆, session超时等问题 ) 一.设置web.config相关选项 先启用窗体身份验证和默认登陆页, 如下. <authentication mode = "Forms"> <forms loginUrl = "default.aspx"></forms> </authentication> 设置网站可以匿名访问, 如下 <authorization> <allow users = "*" /> </authorization> 然后设置跟目录下的admin目录拒绝匿名登陆, 如下.注意这个小节在System.Web小节下面. <location path = "admin"> <system.web> <authorization> <deny users = "?"></deny> </authorization> </system.web> </location> 把http请求和发送的编码设置成GB2312, 否则在取查询字符串的时候会有问题, 如下. <globalization requestEncoding = "gb2312" responseEncoding = "gb2312" /> 设置session超时时间为1分钟, 并启用cookieless, 如下. <sessionState mode = "InProc" cookieless = "true" timeout = "1" /> 为了启用页面跟踪, 我们先启用每一页的trace, 以便我们方便的调试, 如下. <trace enabled = "true" requestLimit = "1000" pageOutput = "true" traceMode = "SortByTime" localOnly = "true" /> 二.设置Global.asax文件 处理Application_Start方法, 实例化一个哈西表, 然后保存在Cache里
protected void Application_Start( Object sender, EventArgs e ) { Hashtable h = new Hashtable( ); Context.Cache.Insert( "online", h ); } 在Session_End方法里调用LogoutCache( )方法, 方法源码如下/// 清除Cache里当前的用户, 主要在Global.asax的Session_End方法和用户注销的方法里调用 /// </summary> public void LogoutCache( ) { Hashtable h = ( Hashtable )Context.Cache["online"]; if( h! = null ) { if( h[Session.SessionID]! = null ) h.Remove( Session.SessionID ); Context.Cache["online"] = h; } } 三.设置相关的登陆和注销代码 登陆前调用PreventRepeatLogin( )方法, 这个方法可以防止用户重复登陆, 如果上次用户登陆超时大于1分钟, 也就是关闭了所有admin目录下的页面达到60秒以上, 就认为上次登陆的用户超时, 你就可以登陆了, 如果不超过60秒, 就会生成一个自定义异常.在Cache["online"]里保存了一个哈西表, 哈西表的key是当前登陆用户的SessionID, 而Value是一个ArrayList, 这个ArrayList有两个元素, 第一个是用户登陆的名字第二个元素是用户登陆的时间, 然后在每个admin目录下的页刷新页面的时候会更新当前登陆用户的登陆时间, 而只admin目录下有一个页打开着, 即使不手工向服务器发送请求, 也会自动发送一个请求更新登陆时间, 下面我在页面基类里写了一个函数来做到这一点, 其实这会增加服务器的负担, 但在一定情况下也是一个可行的办法./// 防止用户重复登陆, 在用户将要身份验证前使用/// </summary> /// <param name = "name">要验证的用户名字</param> private void PreventRepeatLogin( string name ) { Hashtable h = ( Hashtable )Cache["online"]; if( h! = null ) { IDictionaryEnumerator e1 = h.GetEnumerator( ); bool flag = false; while( e1.MoveNext( ) ) { if( ( string )( ( ArrayList )e1.Value )[0] == name ) { flag = true; break; } } if( flag ) { TimeSpan ts = System.DateTime.Now.Subtract( Convert.ToDateTime( ( ( ArrayList )e1.Value )[1] ) ); if( ts.TotalSeconds<60 ) throw new oa.cls.MyException( "对不起, 你输入的账户正在被使用中, 如果你是这个账户的真正主人, 请在下次登陆时及时的更改你的密码, 因为你的密码极有可能被盗窃了!" ); else h.Remove( e1.Key ); } } else { h = new Hashtable( ); } ArrayList al = new ArrayList( ); al.Add( name ); al.Add( System.DateTime.Now ); h[Session.SessionID] = al; if( Cache["online"] == null ) { Context.Cache.Insert( "online", h ); } else Cache["Online"] = h; } 用户注销的时候调用上面提到LogoutCache( )方法 四.设置admin目录下的的所有页面的基类 using System; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; using System.Collections; namespace oa.cls { public class MyBasePage : System.Web.UI.Page { つ柯? 我这里整个程序在OA的虚拟目录下, 受? さ哪柯际莂dmin目录 { get { return Request.FilePath.IndexOf( "/oa/admin" ) == 0; } } private void PreventSessionTimeout( ) { if( !this.IsAdminDir ) return; if( Session["User_Name"] == null&&this.IsAdminDir ) { System.Web.Security.FormsAuthentication.SignOut( ); this.Alert( "登陆超时", Request.ApplicationPath ) } } private void UpdateCacheTime( ) { Hashtable h = ( Hashtable )Cache["online"]; if( h! = null ) { ( ( ArrayList )h[Session.SessionID] )[1] = DateTime.Now; } Cache["Online"] = h; } private void TraceValues( Hashtable myList ) { IDictionaryEnumerator myEnumerator = myList.GetEnumerator( ); int i = 0; while ( myEnumerator.MoveNext( ) ) { Context.Trace.Write( "onlineSessionID"+i, myEnumerator.Key.ToString( ) ); ArrayList al = ( ArrayList )myEnumerator.Value; Context.Trace.Write( "onlineName"+i, al[0].ToString( ) ); Context.Trace.Write( "onlineTime"+i, al[1].ToString( ) ); TimeSpan ts = System.DateTime.Now.Subtract( Convert.ToDateTime( al[1].ToString( ) ) ); Context.Trace.Write( "当前的时间和此登陆时间间隔的秒数", ts.TotalSeconds.ToString( ) ); i++; } } protected void Alert( string msg, string url ) { string scriptString = "<script language = JavaScript>alert( ""+msg+"" ); location.href = ""+url+""</script>"; if( !this.IsStartupScriptRegistered( "alert" ) ) this.RegisterStartupScript( "alert", scriptString ); } protected void XmlReLoad( ) { System.Text.StringBuilder htmlstr = new System.Text.StringBuilder( ); htmlstr.Append( "<SCRIPT LANGUAGE = "JavaScript">" ); htmlstr.Append( "function GetMessage( ) { " ); htmlstr.Append( " var xh = new ActiveXObject( "Microsoft.XMLHTTP" ); " ); htmlstr.Append( " xh.open( "get", window.location, false ); " ); htmlstr.Append( " xh.send( ); " ); htmlstr.Append( " window.setTimeout( "GetMessage( )", 60000 ); " ); htmlstr.Append( " } " ); htmlstr.Append( "window.onload = GetMessage( ); " ); htmlstr.Append( "</SCRIPT> " ); if( !this.IsStartupScriptRegistered( "xmlreload" ) ) this.RegisterStartupScript( "alert", htmlstr.ToString( ) ); } override protected void OnInit( EventArgs e ) { base.OnInit( e ); this.PreventSessionTimeout( ); this.UpdateCacheTime( ); this.XmlReLoad( ); if( this.Cache["online"]! = null ) { this.TraceValues( ( System.Collections.Hashtable )Cache["online"] ); } } } } 五. 写一个自定义异常类 首先要在跟目录下写一个错误显示页面ShowErr.aspx, 这个页面根据传递过来的查询字符串msg的值, 在一个Label上显示错误信息. using System; namespace oa.cls { public class MyException:ApplicationException { public MyException( ):base( ) { } public MyException( string Message ):base( Message ) { System.Web.HttpContext.Current.Response.Redirect( "~/ShowErr.aspx?msg = "+Message ); } public MyException( string Message, Exception InnerException ):base( Message, InnerException ) { } } } 六.总结 我发现在Session里保存的值, 比如session["name"]是没有任何向服务器的请求达到1分钟后就会自动丢失, 但是session ID是关闭同一进程的浏览器页面后达1分钟后才会丢失并更换的, 因为只要你开着浏览器就会有session ID, 无论是在url里保存还是在cookies里.不知道这个结论对不对, 反正我在设置了session的timeout为1分钟后, session["name"]的值已经没有了, 可是SessionID还是旧的, Global.asax里的Session_End里的代码也没有执行, 而身份验证票据也没有丢失.我不知道这三者之间的关系是怎样的, 谁先谁后, 好像在<authentication>小节可以设置一个timeout属性, 不过项目赶的紧, 我没时间研究了. 以上这些代码比较零散, 我花费了2天的时间才总结出来这套方案, 不是很完美, 但是暂时只能这样了, 不能在这方面浪费很多时间了, 大家可以把上面的代码组织到一个类里, 然后把方法都修改成静态方法方便调用. 最后大家有什么建议和改进的意见欢迎和我交流.
本文由中国C#技术学习中心整理 如果你对本文有不明之处请到技术论坛讨论!
|