惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
月光博客
月光博客
AI
AI
B
Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
C
CXSECURITY Database RSS Feed - CXSecurity.com
WordPress大学
WordPress大学
GbyAI
GbyAI
L
Lohrmann on Cybersecurity
O
OpenAI News
Schneier on Security
Schneier on Security
P
Palo Alto Networks Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
Troy Hunt's Blog
V2EX - 技术
V2EX - 技术
W
WeLiveSecurity
L
LINUX DO - 最新话题
人人都是产品经理
人人都是产品经理
S
Security Affairs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
A
Arctic Wolf
Recorded Future
Recorded Future
Microsoft Security Blog
Microsoft Security Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
G
GRAHAM CLULEY
N
Netflix TechBlog - Medium
TaoSecurity Blog
TaoSecurity Blog
C
Check Point Blog
Scott Helme
Scott Helme
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Apple Machine Learning Research
Apple Machine Learning Research
PCI Perspectives
PCI Perspectives
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Vercel News
Vercel News
The Hacker News
The Hacker News
Y
Y Combinator Blog
Latest news
Latest news
SecWiki News
SecWiki News
Hugging Face - Blog
Hugging Face - Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Google Online Security Blog
Google Online Security Blog
Webroot Blog
Webroot Blog
Google DeepMind News
Google DeepMind News
Recent Commits to openclaw:main
Recent Commits to openclaw:main
C
Cisco Blogs
博客园_首页
H
Hackread – Cybersecurity News, Data Breaches, AI and More
宝玉的分享
宝玉的分享
L
LINUX DO - 热门话题

博客园 - limeiky

无法打开 物理 文件 XXX.mdf"。操作系统错误 5:"5(拒绝访问。)"的解决办法 【2026】最简单的白嫖百度文库方法 如何做到子DIV相对DIV底部对齐 idea 连接 MySQL 8.0 以上遇到 Access denied for user ‘root‘@‘localhost‘ (using password: YES)密码错误的问题 idea解决程序包不存在报错 正则表达式解析 Vue之slot插槽和作用域插槽 ant design vue 中的表单校验 v-decorator的使用 SQL更新固定时间显示格式的时间字段 Vue中 let _this = this的作用 vue中的箭头函数 => vue-router 基本使用 c:forEach 标签中遍历map集合 qrcode.js插件,将文字内容转换成二维码格式 JS生成一维码(条形码)功能示例 qrcode.js插件,将文字内容转换成二维码格式 在一个DaoImpl实现中调用另一个DaoImpl中的方法 echarts柱状图坐标文字显示不完整解决方式 Myeclipse在debug模式下没加断点程序卡住,start模式下可以正常启动
如何限制同一用户同时在不同客户端登录?
limeiky · 2020-06-22 · via 博客园 - limeiky

    在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