如何限制同一用户同时在不同客户端登录?
转自:《如何限制同一客户端登录的用户数量以及禁止同一用户同时在不同客户端登录?》
在web应用系统中,出于安全性考虑,经常需要对同一客户端登录的用户数量和一个客户同时在多个客户端登陆进行限制。具体一点就是:
1、在同一台电脑上一次只允许有一个用户登录系统,2、一个用户在同一时间只允许在一个客户端登录。
我最近做的一个系统就遇到了这样的问题,本来系统已经开发完成了,但是安全测评没有通过,就是因为没有做这两个限制。怎么来做这样的限制呢?我在网上找了很久,发现问这个问题的人很多,但是没有找到特别清楚的答案。后来自己摸索着,看了一些书,终于找到解决办法了。
要解决这个问题实际上不难,对于高手来说可能都懒得去说了,但是对于不熟悉web编程的人来说可能会困扰很久。下面我把我的解决办法说出来,供大家参考!
先介绍一下我那个系统的背景:j2ee,tomcat,没有用cookie。
首先确定解决这两个问题的基本思路:
1、要解决同一台电脑上只允许有一个用户登录系统,只有一个办法。监视每一个连接的来源,如果发现有一个新的连接与某个已经存在的连接来自同一台电脑,则终止其中的一个(当然,也可以提醒用户,让他自己决定终止哪一个)。
2、要禁止一个用户账号同时在不同的客户端登录,只有监视每一个连接的用户账号,如果发现一个新连接的用户账号跟某个已经存在的连接的用户账号相同,则自动将前一个终止(同样,也可以让用户自己决定终止哪一个)。
确定了基本思路以后,就要找具体办法了。我最初的想法是在数据库建立一张表,存放已登录用户的用户名、物理地址、Session id等信息。当用户登录时,与这张表里面的数据进行匹配,如果发现物理地址与表中的某条记录相同,则表示是同一台客户端上有多个用户再登录,如果发现正在登录的用户的用户名与表中已有记录相同而主机名不同,则表示是一个账号同时在不同的客户端使用。
相信很多一开始遇到这个问题的人都会考虑这种解决办法。但是这种办法有很多问题,最主要的问题有两个:第一是效率,每一次都要从数据库里面取数据进行匹配。第二是用户退出时需要删除表中的记录,而当用户非正常退出时,很难及时监测(后来发现其实有办法监测)。
后来在网上的某个帖子里面看到一位大侠提到用监听器,只是那位大侠说的太含糊,照他说的办法根本无法解决。虽然无法解决,但是提供了一个思路。于是我找了一本书,仔细看了其中关于监听器的部分。解决办法就在其中了!!!
监听器的详细介绍见我的下一篇博文,这里先把解决办法告诉大家:
监听器可以监听Session及其所包含的属性,即Attribute。
所以我们要做的就是:
1、建立一个监听器,实现HttpSessionAttributeListener接口,监听每一个Attribute的增加、编辑、删除事件。监听器中还要建立一个map,将所有的session放入这个map中。
2、在用户登录时将用户名、物理地址、Session id存到Session中去(可以建立一个用户登录地址数据传输对象,我建立了一个UserSessionAdd类,里面包含username,macAdd,sessionId三个属性,用户登录时将这个数据对象初始化,并存入到session中)。
3、每个新会话开启时,在监听器中对Session包含的属性进行判断,如果新增的属性与map中已有session的用户登录地址数据相同,则表示新会话与我们要做的两个限制相冲突。将与之冲突的会话提取出来,销毁掉!
这么说,还是不够清楚,下面看代码:
Web.xml文件:
<listener>
<listener-class>监听器类的路径,如:com.web.MyListener</ listener-class >
</listener>
用户登录地址数据传输对象:
public class UserSessionAdd {
private String addHost;//主机
private String sessid; //会话窗口Id
private String username;//用户名
private Integer userId; //用户Id
public String getUsername(){
return username;
}
public void setUsername(String username){
this.username=username;
}
public String getAddHost() {
return addHost;
}
public void setAddHost(String addHost) {
this.addHost = addHost;
}
public String getSessid() {
return sessid;
}
public void setSessid(String sessid) {
this.sessid = sessid;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
}
用户登录的代码:
String userHost = request.getRemoteHost();
String sessionId = request.getSession().getId();
UserSessionAdd usa = new UserSessionAdd();
usa.setUserId(userId);
usa.setUsername(username);
usa.setSessid(sessionId);
usa.setAddHost(userHost);
request.getSession().setAttribute(“usa”,usa);
监听器代码:
public class MyListener implements HttpSessionAttributeListener{
Map<Integer, HttpSession> map = new HashMap<Integer, HttpSession>();//将所有的登录的session放入这个map中
public void attributeAdded(HttpSessionBindingEvent event) {
String name = event.getName();//session中保存的对象名
if(name.equals("usa")){
UserSessionAdd usa = (UserSessionAdd)event.getValue();//获取“最新的”当前登录用户的主机地址和会话窗口数据传输对象
if(map.get(usa.getUserId())!=null){//map中存在当前登录用户的id
HttpSession sess = map.get(usa.getUserId());//获取map中“先前的”保存的当前登录用户的会话
UserSessionAdd usa1 = (UserSessionAdd)sess.getAttribute("usa");
sess.removeAttribute("usa");//在“先前的”会话窗口中移除当前用户登录的数据传输对象
sess.invalidate();//令“先前的”会话窗口失效
}
map.put(usa.getUserId(), event.getSession());//将“最新的”当前登录用户的主机地址和会话窗口放入map
}
}
public void attributeRemoved(HttpSessionBindingEvent event) {
String name = event.getName();
if(name.equals("usa")){
UserSessionAdd usa = (UserSessionAdd)event.getValue();
map.remove(usa.getUserId());
}
}
public void attributeReplaced(HttpSessionBindingEvent event) {
// TODO Auto-generated method stub
````
}
}
————————————————
原文链接:https://blog.csdn.net/yutan_313/java/article/details/5405934