一套.net窗体身份验

一套.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( )方法, 方法源码如下
/// <summary>
/// 清除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>
/// 防止用户重复登陆, 在用户将要身份验证前使用
/// </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
  {
    /// <summary>
    /// 获取本页是否在受?
  つ柯? 我这里整个程序在OA的虚拟目录下, 受?
  さ哪柯际莂dmin目录
    /// </summary> protected bool IsAdminDir
    {
        get
        {
          return Request.FilePath.IndexOf( "/oa/admin" ) == 0;
        }
    }
    /// <summary>
    /// 防止session超时, 如果超时就注销身份验证并提示和转向到网站默认页
    /// </summary>
    private void PreventSessionTimeout( )
    {
        if( !this.IsAdminDir ) return;
        if( Session["User_Name"] == null&&this.IsAdminDir )
        {
          System.Web.Security.FormsAuthentication.SignOut( );
          this.Alert( "登陆超时", Request.ApplicationPath )
        }
    }
    /// <summary>
    /// 每次刷新本页面的时候更新Cache里的登陆时间选项, 在下面的OnInit方法里调用.
    /// </summary>
    private void UpdateCacheTime( )
    {
        Hashtable h = ( Hashtable )Cache["online"];
        if( h! = null )
        {
          ( ( ArrayList )h[Session.SessionID] )[1] = DateTime.Now;
        }
        Cache["Online"] = h;
    }
    /// <summary>
    /// 在跟踪里输出一个HashTable的所有元素, 在下面的OnInit方法里调用.以便方便的观察缓存数据
    /// </summary>
    /// <param name = "myList"></param>
    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++;
        }
    }
    /// <summary>
    /// 弹出信息并返回到指定页
    /// </summary>
    /// <param name = "msg">弹出的消息</param>
    /// <param name = "url">指定转向的页面</param>
    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 );
    }
    /// <summary>
    /// 为了防止常时间不刷新页面造成会话超时, 这里写一段脚本, 每隔一分钟向本页发送一个请求以维持会话不被超时, 这里用的是xmlhttp的无刷新请求
    /// 这个方法也在下面的OnInit方法里调用
    /// </summary>
    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
{
 
  /// <summary>
  /// MyException 的摘要说明.
  /// </summary>
  public class MyException:ApplicationException
  {
    /// <summary>
    /// 构造函数
    /// </summary>
    public MyException( ):base( )
    {
    }
    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name = "ErrMessage">异常消息</param>
    public MyException( string Message ):base( Message )
    {
        System.Web.HttpContext.Current.Response.Redirect( "~/ShowErr.aspx?msg = "+Message );
    }
    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name = "Message">异常消息</param>
    /// <param name = "InnerException">引起该异常的异常类</param>
    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天的时间才总结出来这套方案, 不是很完美, 但是暂时只能这样了, 不能在这方面浪费很多时间了, 大家可以把上面的代码组织到一个类里, 然后把方法都修改成静态方法方便调用.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值