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.asaxSession_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的虚拟目录下,受保护的目录是admin目录
              /// </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",
script
String);
   }
  
   /// <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.οnlοad=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里。不知道这个结论对不对,反正我在设置了sessiontimeout1分钟后,session["name"]的值已经没有了,可是SessionID还是旧的,Global.asax里的Session_End里的代码也没有执行,而身份验证票据也没有丢失。我不知道这三者之间的关系是怎样的,谁先谁后,好像在<authentication>小节可以设置一个timeout属性,不过项目赶的紧,我没时间研究了。

以上这些代码比较零散,我花费了2天的时间才总结出来这套方案,不是很完美,但是暂时只能这样了,不能在这方面浪费很多时间了,大家可以把上面的代码组织到一个类里,然后把方法都修改成静态方法方便调用。

 

MSDN 2003 中搜索一下 <sessionState> 即可看到关于 Web.config 中的<sessionState> 节点元素的描述,共有 OffInProcStateServerSQLServer 四种模式。OffInProc 分别指“不启用”、“进程内保存(默认值)”,此两种模式没啥讲的,所谓 InProc 就是把 Session 保存在 aspnet_wp.exe (Windows 2000 解析 ASP.NET页面所用的进程) w3wp.exe (Win2003 的进程) 中,一旦进程被中止或被重置,Session 将丢失。

 

一、引发 Session 丢失的几种原因

 

动过手写代码的人都知道,Session 丢失是比较常见的事。以下是本人这几年所遇到的,能够引发 Session 丢失的原因,不敢说是百分百,丢失概率还是特别高的。错…,简直可以说是“相…当…”高哇 ^_^"

 

1、存放 Session 的电脑重启(废话,若这样都不丢,你神仙啊)

 

2InProc 模式:aspnet_wp.exe w3wp.exe 在“任务管理器”中或其它情况下导致其进程被终止运行。

 

3InProc 模式:修改 .cs 文件后,编译了两次(只编译一次,有时不会丢失)

 

4InProc 模式:修改了 Web.config

 

5InProc 模式,Windows 2003 环境:应用程序池回收、停止后重启

 

6InProc 模式:服务器上 bin 目录里的 .dll 文件被更新

 

以上列举的都是 InProc 模式下,容易引发解析 ASP.NET 应用程序重置的原因。是不是觉得很窝火?之前我也有这种感觉,慢慢就习惯啦,再后来就干脆不用这种模式了。于是乎,就有了使用下列两种模式的尝试,现写出来与大家一起分享。

 

二、使用 StateServer 保存 Session

 

StateServer 模式的实质是,把Session 存放在一个单独的进程里,此进程独立于 aspnet_wp.exe w3wp.exe 。启用此服务后,在“任务管理器”中可以看到一个名为 aspnet_state.exe 的进程,下面开始说明一下设置的具体步骤:

 

1、修改注册表(关键步骤,如下图)

 

运行 regedit 打开注册表 找到HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/aspnet_state/Parameters 节点 AllowRemoteConnection 的键值设置成“1”(1 为允许,0 代表禁止)→ 设置 Port (端口号)

 

注意事项:

 

       a)、若ASP.NET State Service 正在运行,修改注册表内容后,则需要重新启动该服务

 

b)、注意端口号的键值是以十六进制储存的,可以使用十进制进行修改,42424 是默认的端口

 

c)AllowRemoteConnection 的键值设置成“1后,意味着允许远程电脑的连接,也就是说只要知道你的服务端口,就可享用你的ASP.NET State Service,即把 Session 存放在你的电脑进程内,因此请大家慎用;键值为“0时,仅有stateConnectionString 为“tcpip=localhost: 42424与“tcpip=127.0.0.1:42424的情况,方可使用ASP.NET State Service

 

 

 

2    开启 ASP.NET State Service(如下图)

 

右键点击“我的电脑”→ 管理 服务与应用程序 服务 双击“ASP.NET State Service 启动(可设为“自动”)

 

说明:只要安装了 .Net Framework v1.0/v1.1 ,都拥有此服务。

 

3    更改 Web.config

 

打开 Web.config 找到 <sessionState> 节点内容

 

<sessionState

 

            mode="InProc"

 

            stateConnectionString="tcpip=127.0.0.1:42424"

 

            sqlConnectionString="data source=127.0.0.1;Trusted_Connection=yes"

 

            cookieless="false"

 

            timeout="20" />

 

 

 

将其改为以下内容

 

<sessionState mode="StateServer"  stateConnectionString="tcpip=192.168.0.2:42424" timeout="20" />

 

注意事项:

 

       a)、设成StateServer 后,必须要有对应的stateConnectionString

 

       b)、注意 IP 地址(可以是远程计算机 IP、计算机名称、域名)与端口号,端口号需与ASP.NET State Service 的服务端口一致

 

三、        Session 放入 SQLServer 保存

 

SQLServer 模式就是,把Session 存放在 SQL Server 数据库里(注意不是 Oracle ,动动脚趾都能猜到原因啦),下面开始说明一下设置的具体步骤:

 

1    启动相关的数据库服务(如图)

 

运行SQL Server 服务管理器 启动 SQL Server (最好设为开机自动运行) 启动 SQL Server Agent 服务(最好设为开机自动运行)

 

注意事项:

 

       a)、注意启动顺序,也可通过下列方式设置: 右键点击“我的电脑”→ 管理 服务与应用程序 服务 找到“MSSQLSERVER”与“SQLSERVERAGENT 启动并设置启动类型为“自动”

 

b)SQL Server Agent在此处的作用是清除数据库中已过期的 Session

 

2    建立存放 Session DataBase

 

运行“SQL 查询分析器”→ 使用“sa”或是拥有“master”的 db_owner 权限的用户登录数据库 打开查询文件 C:/WINNT/Microsoft.NET/Framework/v1.1.4322/InstallSqlState.sql (存放在 Windows 系统目录的 .Net 安装目录下可找到) 直接运行该 sql 脚本 刷新数据库即可看到名为 ASPState DataBase

 

3    建立连接数据库 ASPState 的用户,并为此用户授权(此步骤可跳过)

 

进行此步的原因是:一是不想在 Web.config 中出现 sa 的密码;二是 tempdb 在数据库启动后仅保留 sa 一个帐号的使用权限,其余帐号的权限统统被清除,但保存 Session又需要用到此 DataBase

A)、运行 SQL Server 的企业管理器 展开数据库的安全性 右击“登录” 新建“登录” 输入“名称” 选择 SQL Server 身份验证” 输入“密码” 指定“数据库” 点击“数据库访问” 勾选 ASPState 选中“db_owner”角色 点击“确定” 再一次输入“密码” 点击“确定” 后即可建立 ASPState 的用户(此处建立名为“SessionStateUser”,密码为“123456的测试用户)

 

B)、运行 SQL Server 的企业管理器 展开“管理” 展开“SQL Server 代理” 右击“作业” 点击“新建作业” 输入 “名称”(此例为 GrantSessionUser 点击标签 “步骤” 新建 输入 “步骤名”(此例为 Grant01 选择数据库“tempdb 编写 SQL 脚本“exec sp_adduser 'SessionStateUser', 'SessionUser' ,'db_owner' ”→ 确定 点击标签 “调度” 新建 输入 “名称”(此例为 Start01 )→ 选择类型“SQL Server 代理启动时自动启动” 确定 最后点击“确定”新增完毕

 

4    设置 Web.config 内容

 

打开 Web.config 找到 <sessionState> 节点内容 修改为以下内容即可:

 

<sessionState mode="SQLServer"  sqlConnectionString ="data source=192.168.0.2; user id= SessionStateUser; password=123456" timeout="20" />

 

注意事项:

 

a)sqlConnectionString 中不能出现 initial catalog 选项

 

b)SQL Server Agent在此处的作用是清除数据库中已过期的 Session

 

c)、你若跳过了第三步,则 user id 需要用 sa 进行登录

 

d)、若sqlConnectionString data source=127.0.0.1;Trusted_Connection=yes”,则使用本地计算机ASPNETWindows 2000 系统帐户)或 Network ServiceWindows 2003 系统帐户)的身份登录数据库。要是数据库不允许上述用户登录,则报错;同样,即使上述帐户能成功登录,也要分配其 tempdb 的权限,理由是 Session 是保存在 tempdb 中的,若没有该 DataBase 的存取权限是行不滴。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值