人无信不立/2008-04-26 22:30

防止一个用户登录多次的方法

在web开发时,有的系统要求同一个用户在同一时间只能登录一次,也就是如果一个用户已经登录了,在退出之前如果再次登录的话需要报错。
常见的处理方法是,在用户登录时,判断此用户是否已经在Application中存在,如果存在就报错,不存在的话就加到Application中(Application是所有Session共有的,整个web应用程序唯一的一个对象):
        string strUserId = txtUser.Text;
        
        ArrayList list 
= Application.Get("GLOBAL_USER_LIST"as ArrayList;
        
if (list == null)
        
{
            list 
= new ArrayList();
        }

        
for (int i = 0; i < list.Count; i++)
        
{
            
if (strUserId == (list[i] as string))
            
{
                
//已经登录了,提示错误信息
                lblError.Text = "此用户已经登录";
                
return;
            }

        }


        list.Add(strUserId);
        Application.Add(
"GLOBAL_USER_LIST", list);
当然这里使用Cache等保存也可以。

接下来就是要在用户退出的时候将此用户从Application中去除,我们可以在Global.asax的Session_End事件中处理:
    void Session_End(object sender, EventArgs e) 
    
{
        
// 在会话结束时运行的代码。 
        
// 注意: 只有在 Web.config 文件中的 sessionstate 模式设置为
        
// InProc 时,才会引发 Session_End 事件。如果会话模式设置为 StateServer 
        
// 或 SQLServer,则不会引发该事件。
        string strUserId = Session["SESSION_USER"as string;
        ArrayList list 
= Application.Get("GLOBAL_USER_LIST"as ArrayList;
        
if (strUserId != null && list != null)
        
{
            list.Remove(strUserId);
            Application.Add(
"GLOBAL_USER_LIST", list);
        }

    }

这些都没有问题,有问题的就是当用户直接点浏览器右上角的关闭按钮时就有问题了。因为直接关闭的话,并不会立即触发Session过期事件,也就是关闭浏览器后再来登录就登不进去了。
这里有两种处理方式:
1、使用Javascript方式
   在每一个页面中加入一段javascript代码:
   
function window.onbeforeunload()
{
    
if (event.clientX>document.body.clientWidth && event.clientY<0||event.altKey)
        window.open(
"logout.aspx");
    }

}

由于onbeforeunload方法在浏览器关闭、刷新、页面调转等情况下都会被执行,所以需要判断是点击了关闭按钮或是按下Alt+F4时才执行真正的关闭操作。
然后在logout.aspx的Page_Load中写和Session_End相同的方法,同时在logout.aspx中加入事件:onload="javascript:window.close()"

但是这样还是有问题,javascript在不同的浏览器中可能有不同的行为,还有就是当通过文件->关闭时没有判断到。

2、使用xmlhttp方法(这种方法测试下来没有问题)
在每个页面中加入如下的javascript(这些javascript也可以写在共通里,每个页面引入就可以了)

var x=0;
function myRefresh()
{
    var httpRequest 
= new ActiveXObject("microsoft.xmlhttp");
    httpRequest.open(
"GET""test.aspx"false);
    httpRequest.send(
null);
    x
++;
    
if(x<60)   //60次,也就是Session真正的过期时间是30分钟
    {
        setTimeout(
"myRefresh()",30*1000); //30秒
    }

}

myRefresh();


在web.config中设置
    <sessionState mode="InProc" timeout="1"></sessionState>

test.aspx页面就是一个空页面,只不过需要在Page_Load中加入:
        Response.Expires = -1;
保证不使用缓存,每次都能调用到这个页面。

原理就是:设置Session的过期时间是一分钟,然后在每个页面上定时每30秒连接一次测试页面,保持Session有效,总共连60次,也就是30分钟。如果30分钟后用户还没有操作,Session就会过期。当然,如果用户直接关闭浏览器,那么一分钟后Session也会过期。这样就可以满足要求了。

posted @ 2007-08-08 09:44 永春 阅读(3565) 评论(31)  编辑 收藏 所属分类: .Net

  回复  引用    
#1楼 2007-08-08 09:54 | 路人甲 [未注册用户]
如果同时打开了多个页面,然后关闭其中一个页面的情况。
  回复  引用    
#2楼 2007-08-08 09:56 | kingcat [未注册用户]
新的问题又出来了,你老请求服务器,人一多了,服务器的压力也大了
  回复  引用  查看    
#3楼 2007-08-08 10:03 | 站在天空下的猪      
如果用户 一次打开了很多页面,关闭其中一个
也容易出问题呢
  回复  引用  查看    
#4楼 2007-08-08 10:05 | onekey      
按alt+tab切换窗口就重新登录啊
  回复  引用    
#5楼 2007-08-08 10:07 | kevin [未注册用户]
这个是浏览器的本身限制,HTTP本来就是无状态的连接,没有办法太精确的.
  回复  引用  查看    
#6楼 [楼主]2007-08-08 10:21 | GSpring      
@路人甲
@onekey
@站在天空下的猪
使用xmlhttp方法就没有这个问题了
  回复  引用  查看    
#7楼 [楼主]2007-08-08 10:23 | GSpring      
@kingcat
的确有性能的问题

正如kevin所说的,想精确控制的确比较麻烦,只能用性能去换了。
  回复  引用    
#8楼 2007-08-08 10:26 | A.Z [未注册用户]
不用asp.net呢?
  回复  引用  查看    
#9楼 [楼主]2007-08-08 10:32 | GSpring      
@A.Z
其实Java也有同样的问题,web开发可能都有这个问题,主要是由http限制的
  回复  引用    
#10楼 2007-08-08 10:50 | ly [未注册用户]
ctrl+alt+del直接终止ie进程或者IE由于某些原因崩溃呢?
  回复  引用  查看    
#11楼 2007-08-08 10:50 | 深蓝      
总的来说还是一个不错的方法
  回复  引用  查看    
#12楼 [楼主]2007-08-08 10:53 | GSpring      
@ly
使用xmlhttp方法,就算崩溃时,一分钟后也会自动过期的
  回复  引用    
#13楼 2007-08-08 11:10 | BA [未注册用户]
多此一举,每个用户登录肯定会写Session,比如是Session["userid"],那么只需要判断Session["userid"]是不是为null,为null表时未登录,否则已登录,如果要限制两次登录,如果不为null,直接返回消息提示不能重复登录
  回复  引用  查看    
#14楼 [楼主]2007-08-08 11:28 | GSpring      
@BA
-_-,我不知道说什么,我要判断的是在不同机器上的多次登录
  回复  引用  查看    
#15楼 2007-08-08 11:41 | ˇ┾落≠單_      
在内存开辟一块区域来记录登录用户,退出登录时清除记录.
这样可以减少服务器负担.

  回复  引用  查看    
#16楼 2007-08-08 11:43 | 双鱼座      
的确是多此一举。Cookie就是Http提供的解决方案。
  回复  引用  查看    
#17楼 2007-08-08 11:49 | S.Sams      
使用 microsoft.xmlhttp 去保持数据的失效性 sessionState mode="InProc" timeout="1", 也就是说, 得在所有的页面中都载入此操作, 你的服务器性能一定非常的Good吧, 不可取!
  回复  引用    
#18楼 2007-08-08 11:54 | dominic [未注册用户]
我所在单位对外有3条出口,为了做到流量负载均衡,访问外部时会在三条线路(不同)之间随机选择,考虑一下这种情况吧。

以前的单位用两台ISA做对外负载均衡也是两个IP随机换。
  回复  引用  查看    
#19楼 [楼主]2007-08-08 12:03 | GSpring      
@双鱼座
cookie实现不了这个功能吧

  回复  引用  查看    
#20楼 [楼主]2007-08-08 12:05 | GSpring      
@dominic
有负载均衡的话,可以把原来保存在Application中的信息,保存到数据库中
  回复  引用  查看    
#21楼 [楼主]2007-08-08 12:06 | GSpring      
@S.Sams
性能的确是个问题。

不过我一直没有找到好的解决方法,不知道各位有没有好的建议?
  回复  引用  查看    
#22楼 2007-08-08 14:02 | 金色海洋(jyk)      
03年做项目的时候,我的前辈就用的XMLHttp的方式,时间间隔是1分钟。
  回复  引用    
#23楼 2007-08-08 14:15 | fc [未注册用户]
难道不是在db里面存一个session,每次login的时候check一下不就好了?
  回复  引用    
#24楼 2007-08-08 21:42 | kisskiki [未注册用户]
◎cookie实现不了这个功能吧
其实很类似与你的application方法,只不过application是服务器端,而cookie是客户端,当然如果客户端禁止cookie了,这样也不行
  回复  引用    
#25楼 2007-08-08 22:44 | hehe [未注册用户]
其实采用异步检查的方式,也就是xmlHttp是目前比较可行的方式,我也采用这种方式.在ie下应该没有什么问题.
  回复  引用    
#26楼 2007-08-09 10:23 | long [未注册用户]
@BA
你的理解有问题。不同的机器Sessionid会一样吗?
  回复  引用  查看    
#27楼 2007-08-09 13:13 | 布尔      
登录时记录下来其innerIP outerIP,用户操作数据或者访问功能时对比一下isIPOK。eg:
userA 在 192.168.1.2 202.101.11.22登录系统
之后
userA 又在192.168.1.2 202.101.11.33登录
这时登录标志已经改变
userA访问某功能或者操作数据时isIPOK()=false,提示帐号已经在别的地方登录,被弹出。QQ也是这个原理。
  回复  引用  查看    
#28楼 [楼主]2007-08-09 13:22 | GSpring      
@布尔
不错

不过这样只能实现后登录的把前面登录的用户冲掉了。
要满足这样的功能,其实只要把SessionID保存在数据库中就就可以了,不用保存IP的
  回复  引用  查看    
#29楼 2007-10-15 13:19 | ∮冰火ㄉぶ      
如果电脑突然断电,又会是什么情况?这样做也不是很好吧!
  回复  引用  查看    
#30楼 [楼主]2007-10-15 17:37 | GSpring      
@∮冰火ㄉぶ
使用xmlhttp方法,就算崩溃,电脑突然断电时,一分钟后也会自动过期的