• java web项目防止多用户重复登录解决方案


    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本人声明。否则将追究法律责任。
    作者:永恒の_☆    地址:http://blog.csdn.net/chenghui0317/article/details/9373345

         目前web项目中,很多情况都是可以让同一个账户信息在不同的登录入口登录这次,这样子就不那么美好了。

    现在有两种解决方案:

        1、将用户的登录信息用一个标志位的字段保存起来,每次登录成功就标记1,注销登录就标记为0,当标记为1的时候不允许别人登录。

        2、将用户的登录信息保存在application内置作用域内, 然后利用session监听器监听每一个登录用户的登录情况。

    很显然,第一种方式 每次登录 都需要操作数据库,多了一些不必要的性能开销,而且在登录状态下 万一突然电脑关闭了,那就永远都不能登录了,可用性比较低。

    但是第二种方式就不一样了,可操作性强,很方便维护所有在线用户的信息。

    接下来 主要介绍第二种方式的具体实现:

        1、在处理登录的login方法中,先查询数据库验证下该用户是否存在,如果存在 判断该登录账户是否已经锁定了, 然后从application内置作用域对象中取出所有的登录信息,查看该username账户是否已经登录,如果登录了,就友好提示下,反之表示可以登录,将该登录信息以键值对的方式保存在application中。

    代码如下:

    1. //没有使用零配置前 每个访问的方法都要加上@Action ,否则404  
    2. @Action(value="login", results={  
    3.         @Result(name="index", location="index.jsp"),  
    4. })  
    5. public String login() throws Exception {  
    6.     try{  
    7.         User result = userService.login(user.getFuUserName(), user.getFuPassword());  
    8.         if(result!=null){  
    9.             if(result.getFuStatus()!=null && result.getFuStatus()==0){  
    10.                 super.setRequestAttr(Constant.MESSAGE, "抱歉,该用户已被锁定!");  
    11.                 return "error";  
    12.             }  
    13.             Map<String, String> loginUserMap = (Map<String, String>) super.getApplicationAttr(Constant.LOGIN_USER_MAP);  
    14.             boolean isExist = false;  
    15.             String sessionId = super.getSessionId(false);  
    16.             if(loginUserMap==null){  
    17.                 loginUserMap = new HashMap<String, String>();  
    18.             }  
    19.             for (String username : loginUserMap.keySet()) {  
    20.                 //判断是否已经保存该登录用户的信息         或者     如果是同一个用户进行重复登录那么允许登录  
    21.                 if(!username.equals(result.getFuUserName()) || loginUserMap.containsValue(sessionId)){  
    22.                     continue;  
    23.                 }  
    24.                 isExist = true;  
    25.                 break;  
    26.             }                 
    27.             if(isExist){  
    28.                 super.setRequestAttr(Constant.MESSAGE, "抱歉,该用户已登录!");  
    29.                 return "error";  
    30.             }else {  
    31.                 loginUserMap.put(result.getFuUserName(), sessionId);  
    32.             }  
    33.             //登录成功  
    34.             super.setSessionAttr(Constant.LOGIN_USER, result);  
    35.             super.setApplicationAttr(Constant.LOGIN_USER_MAP, loginUserMap);  
    36.               
    37.             logger.info(result.getFuUserName() + " 登录成功!");  
    38.             //如果 session中fromUrl有值,就跳转到该页面  
    39.             String fromUrl = (String)super.getSessionAttr(Constant.FROM_URL);  
    40.             if(fromUrl!=null){  
    41.                 super.setSessionAttr(Constant.FROM_URL, null);  
    42.                 super.getResponse().sendRedirect(fromUrl.toString());  
    43.                 return null;  
    44.             }  
    45.             return "index";  
    46.         }  
    47.     }  
    48.     catch (Exception e) {  
    49.         e.printStackTrace();  
    50.         logger.info("登录失败: "+e.getMessage());  
    51.     }  
    52.     super.setRequestAttr("message", "用户名或密码错误");  
    53.     return "error";  
    54. }  


        2、登录入口处理完之后,考虑到会话结束的话,那么对应的登录用户也应该相应的注销登录。我们可以写一个Session监听器,监听sessioon销毁的时候,我们将登录的用户注销掉,也就是从application中移除。表示该用户已经下线了。

    代码如下:

    1. package com.facelook.util;  
    2.   
    3. import java.util.Map;  
    4.   
    5. import javax.servlet.http.HttpSessionEvent;  
    6. import javax.servlet.http.HttpSessionListener;  
    7.   
    8. import org.apache.log4j.Logger;  
    9.   
    10. import com.facelook.entity.User;  
    11.   
    12. public class SessionListener implements HttpSessionListener{  
    13.   
    14.     private Logger logger = Logger.getLogger(this.getClass());  
    15.       
    16.     @Override  
    17.     public void sessionCreated(HttpSessionEvent event) {  
    18.           
    19.     }  
    20.   
    21.     @Override  
    22.     public void sessionDestroyed(HttpSessionEvent event) {  
    23.         //在session销毁的时候 把loginUserMap中保存的键值对清除  
    24.         User user = (User)event.getSession().getAttribute("loginUser");  
    25.         if(user!=null){  
    26.             Map<String, String> loginUserMap = (Map<String, String>)event.getSession().getServletContext().getAttribute("loginUserMap");  
    27.             loginUserMap.remove(user.getFuUserName());  
    28.             event.getSession().getServletContext().setAttribute("loginUserMap",loginUserMap);  
    29.         }  
    30.           
    31.     }  
    32.   
    33. }  

    web.xml中配置如下:

    1. <!-- session listener -->  
    2. <listener>  
    3.     <listener-class>com.facelook.util.SessionListener</listener-class>  
    4. </listener>  


        3、另外,还有一个问题,如果说登录的用户突然关闭了浏览器或者页面而没有点击退出按钮。那么可以利用beforeunload 事件,在浏览器刷新或者关闭的时候触发。

    1. //在刷新或关闭时调用的事件  
    2. $(window).bind('beforeunload',function(){  
    3.   $.ajax({  
    4.     url:"${ctx}/system/user/user!logout.action",  
    5.     type:"post",  
    6.     success:function(){  
    7.         alert("您已退出登录");  
    8.     }  
    9. });  
    10. );  

    但是如果一些客观原因,比如电脑突然关机,自动重启,等等,这些就没法避免了,所以只能等待服务器端的session会话重置之后才可以再登录。

    除非 做一个 统计所有在线人员的模块,管理员在里面进行在线人员的登录登出的状态管理,把那些有问题的登录用户直接销毁掉。

    接下来简单介绍下在线人员模块的管理:

       1、首先需要一个session监听器来监听所有的回话create的情况,这时候每次创建一个session就可以count+1 ,然后销毁的时候count-1 ,另外还需要一个ServletContext的监听器来监听web应用的生命周期,获取servletContext对象,然后将在线人员总数统计出来存放进去;

    具体代码如下:

    1. package com.facelook.util;  
    2.   
    3. import java.util.Map;  
    4.   
    5. import javax.servlet.ServletContext;  
    6. import javax.servlet.ServletContextEvent;  
    7. import javax.servlet.ServletContextListener;  
    8. import javax.servlet.http.HttpSessionEvent;  
    9. import javax.servlet.http.HttpSessionListener;  
    10.   
    11. import org.apache.log4j.Logger;  
    12.   
    13. import com.facelook.entity.User;  
    14.   
    15. public class SessionListener implements HttpSessionListener,ServletContextListener{  
    16.   
    17.     private int count;  
    18.     private ServletContext servletContext = null;  
    19.       
    20.     public SessionListener() {  
    21.         count = 0;  
    22.     }  
    23.   
    24.     private Logger logger = Logger.getLogger(this.getClass());  
    25.     @Override  
    26.     public void sessionCreated(HttpSessionEvent event) {  
    27.         count++;  
    28.         setContext(event);  
    29.         logger.info("***************the  http session is created...***************");  
    30.     }  
    31.   
    32.     @Override  
    33.     public void sessionDestroyed(HttpSessionEvent event) {  
    34.         //在session销毁的时候 把loginUserMap中保存的键值对清除  
    35.         User user = (User)event.getSession().getAttribute("loginUser");  
    36.         if(user!=null){  
    37.             Map<String, String> loginUserMap = (Map<String, String>)event.getSession().getServletContext().getAttribute("loginUserMap");  
    38.             loginUserMap.remove(user.getFuUserName());  
    39.             event.getSession().getServletContext().setAttribute("loginUserMap",loginUserMap);  
    40.         }  
    41.           
    42.         count--;  
    43.         setContext(event);  
    44.         logger.info("***************the  http session is destroyed...***************");  
    45.     }  
    46.   
    47.     public void setContext(HttpSessionEvent httpSessionEvent){  
    48.         httpSessionEvent.getSession().getServletContext().setAttribute("online", count);  
    49.     }  
    50.       
    51.       
    52.     @Override  
    53.     public void contextDestroyed(ServletContextEvent servletcontextevent) {       
    54.         this.servletContext = null;  
    55.         logger.info("***************the  servlet context is destroyed...***************");  
    56.     }  
    57.   
    58.     @Override  
    59.     public void contextInitialized(ServletContextEvent servletcontextevent) {  
    60.         this.servletContext = servletcontextevent.getServletContext();  
    61.         logger.info("***************the  servlet context is initialized...***************");  
    62.     }  
    63.   
    64. }  

       2、在UserAction中创建管理在线用户的模块的方法,并且支持强制退出的功能;

    1. /** 
    2.  * 退出登录 
    3.  * @return 
    4.  * @throws ServletException 
    5.  * @throws IOException 
    6.  */  
    7. public String logout() throws ServletException, IOException{  
    8.     try {  
    9.         Map<String, String> loginUserMap = (Map<String, String>) super.getApplicationAttr(Constant.LOGIN_USER_MAP);  
    10.         User user = (User) super.getSessionAttr(Constant.LOGIN_USER);  
    11.         super.removeAttribute(Constant.LOGIN_USER_MAP);  
    12.         loginUserMap.remove(user.getFuUserName());  
    13.         super.setApplicationAttr(Constant.LOGIN_USER_MAP,loginUserMap);  
    14.         logger.info("退出登录成功!");  
    15.     } catch (Exception e) {  
    16.         e.printStackTrace();  
    17.         logger.error("退出登录失败: "+e.getMessage());  
    18.     }  
    19.     return INPUT;  
    20. }  
    21.   
    22. /** 
    23.  * 在线用户管理 
    24.  * @return 
    25.  */  
    26. public String loginManager(){  
    27.     return SUCCESS;  
    28. }  
    29.   
    30. /** 
    31.  * 强制退出其他用户 
    32.  * @return 
    33.  */  
    34. public String logoutOther(){  
    35.     try {  
    36.         String username = ServletActionContext.getRequest().getParameter("username");  
    37.         Map<String, String> loginUserMap = (Map<String, String>) super.getApplicationAttr(Constant.LOGIN_USER_MAP);  
    38.           
    39.         if(username!=null && loginUserMap.containsKey(username)){  
    40.             loginUserMap.remove(username);  
    41.             super.setApplicationAttr(Constant.LOGIN_USER_MAP, loginUserMap);              
    42.         }  
    43.     } catch (Exception e) {  
    44.         e.printStackTrace();  
    45.         logger.info("强制退出失败: "+e.getMessage());  
    46.     }  
    47.     return null;  
    48. }  

       3、在管理页面加载在线用户的列表;

    对应的方法定义完毕之后,然后再在对应的管理页面添加在线列表,具体如下:

    1. <%@page import="java.util.Map"%>  
    2. <%@page import="java.util.Map.Entry"%>  
    3. <%@ page language="java" pageEncoding="UTF-8" %>  
    4. <%@ include file="/common/taglib.jsp" %>  
    5. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
    6. <html xmlns="http://www.w3.org/1999/xhtml">  
    7. <head>  
    8. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  
    9. <title>欢迎来到Facelook</title>  
    10. <%@ include file="/common/resource.jsp" %>  
    11. <script type="text/javascript">  
    12.   <!--  
    13.   //在刷新或关闭时调用的事件  
    14.   $(window).bind('beforeunload',function(){  
    15.        $.ajax({  
    16.             url:"${ctx}/system/user/user!logout.action",  
    17.             type:"post",  
    18.             success:function(){  
    19.                 alert("您已退出登录");  
    20.             }  
    21.         });  
    22.    });  
    23.     
    24.   function logout(username){  
    25.         if(username=="${sessionScope.loginUser.fuUserName}"){  
    26.             alert("不允许退出自己账号!");  
    27.             return;  
    28.         }  
    29.         $.ajax({  
    30.             url:"${ctx}/system/user/user!logoutOther.action?username="+username,  
    31.             type:"post",  
    32.             success:function(){  
    33.                 $("#tr"+username).hide();  
    34.                 var count = parseInt($("#count").html());  
    35.                 $("#count").html(count-1);  
    36.                 alert("退出成功!");  
    37.             }  
    38.         });  
    39.     }  
    40.   //-->  
    41. </script>  
    42. </head>  
    43. <body>  
    44. <%@ include file="/common/header.jsp" %>  
    45. <div id="main" class="wrap">  
    46.     <%@ include file="/common/lefter.jsp" %>  
    47.     <div class="righter">  
    48.         <div class="main">  
    49.             <h2>登录列表</h2>  
    50.             <%  
    51.             Map<String,Stringmap = (Map<String,String>)application.getAttribute("loginUserMap");  
    52.             out.println("目前共有<font id='count'>"+map.size()+"</font>个用户在线!!");  
    53.             %>  
    54.             <table border="1" width="400">  
    55.             <%for(Entry<String,String> m : map.entrySet()){%>  
    56.                 <tr id="tr<%=m.getKey()%>">  
    57.                     <td>  
    58.                         <%=m.getKey()%>  
    59.                     </td>  
    60.                     <td width="80">  
    61.                         <href="javascript:logout('<%=m.getKey()%>')">强制退出</a>  
    62.                     </td>  
    63.                 </tr>      
    64.             <%}%>  
    65.             </table>  
    66.         </div>  
    67.     </div>  
    68. </div>   
    69. <%@ include file="/common/footer.jsp" %>  
    70. <%@ include file="/common/message.jsp" %>                      
    71. </body>  
    72. </html>  


    好了启动部署项目,然后启动服务,进入在线用户管理模块,简单效果如下图:

    需要注意的是:当前登录用户 不允许强制退出自己的登录信息。

    这样子,基本上可以实现防止多用户登录的案例了!

  • 相关阅读:
    建设Kubernetes生产环境的16条建议
    深度长文:深入理解Ceph存储架构
    10个最危险的Linux命令,希望你牢记在心
    完美排查入侵者的 10 个方法和 1 个解决思路
    基于Docker&Kubernetes构建PaaS平台基础知识梳理
    Linux入门进阶
    (七)服务接口调用-OpenFeign
    (六)服务调用负载均衡-Ribbon
    (五)Eureka替换方案-Consul
    (四)Eureka替换方案-Zookeeper
  • 原文地址:https://www.cnblogs.com/wnlja/p/4409773.html
Copyright © 2020-2023  润新知