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

来源:互联网 发布:linux c printf 编辑:程序博客网 时间:2024/04/30 02:50
 如果项目用到了Spring Security 3, 它自带了防止重复登陆的功能,只要配置下就可以了。

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

现在有两种解决方案:

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

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

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

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

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

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

代码如下:

[java] view plaincopyprint?

  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. }  

//没有使用零配置前 每个访问的方法都要加上@Action ,否则404@Action(value="login", results={@Result(name="index", location="index.jsp"),})public String login() throws Exception {try{User result = userService.login(user.getFuUserName(), user.getFuPassword());if(result!=null){if(result.getFuStatus()!=null && result.getFuStatus()==0){super.setRequestAttr(Constant.MESSAGE, "抱歉,该用户已被锁定!");return "error";}Map<String, String> loginUserMap = (Map<String, String>) super.getApplicationAttr(Constant.LOGIN_USER_MAP);boolean isExist = false;String sessionId = super.getSessionId(false);if(loginUserMap==null){loginUserMap = new HashMap<String, String>();}for (String username : loginUserMap.keySet()) {//判断是否已经保存该登录用户的信息         或者     如果是同一个用户进行重复登录那么允许登录if(!username.equals(result.getFuUserName()) || loginUserMap.containsValue(sessionId)){continue;}isExist = true;break;}if(isExist){super.setRequestAttr(Constant.MESSAGE, "抱歉,该用户已登录!");return "error";}else {loginUserMap.put(result.getFuUserName(), sessionId);}//登录成功super.setSessionAttr(Constant.LOGIN_USER, result);super.setApplicationAttr(Constant.LOGIN_USER_MAP, loginUserMap);logger.info(result.getFuUserName() + " 登录成功!");//如果 session中fromUrl有值,就跳转到该页面String fromUrl = (String)super.getSessionAttr(Constant.FROM_URL);if(fromUrl!=null){super.setSessionAttr(Constant.FROM_URL, null);super.getResponse().sendRedirect(fromUrl.toString());return null;}return "index";}}catch (Exception e) {e.printStackTrace();logger.info("登录失败: "+e.getMessage());}super.setRequestAttr("message", "用户名或密码错误");return "error";}


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

代码如下:

[java] view plaincopyprint?

  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. }  

package com.facelook.util;import java.util.Map;import javax.servlet.http.HttpSessionEvent;import javax.servlet.http.HttpSessionListener;import org.apache.log4j.Logger;import com.facelook.entity.User;public class SessionListener implements HttpSessionListener{    private Logger logger = Logger.getLogger(this.getClass());        @Override    public void sessionCreated(HttpSessionEvent event) {            }    @Override    public void sessionDestroyed(HttpSessionEvent event) {        //在session销毁的时候 把loginUserMap中保存的键值对清除        User user = (User)event.getSession().getAttribute("loginUser");        if(user!=null){            Map<String, String> loginUserMap = (Map<String, String>)event.getSession().getServletContext().getAttribute("loginUserMap");            loginUserMap.remove(user.getFuUserName());            event.getSession().getServletContext().setAttribute("loginUserMap",loginUserMap);        }            }}

web.xml中配置如下:

[html] view plaincopyprint?

  1. <!-- session listener -->  

  2. <listener>  

  3.     <listener-class>com.facelook.util.SessionListener</listener-class>  

  4. </listener>  

<!-- session listener --><listener><listener-class>com.facelook.util.SessionListener</listener-class></listener>


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

[java] view plaincopyprint?

  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. );  

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

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

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


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

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

具体代码如下:

[java] view plaincopyprint?

  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. }  

package com.facelook.util;import java.util.Map;import javax.servlet.ServletContext;import javax.servlet.ServletContextEvent;import javax.servlet.ServletContextListener;import javax.servlet.http.HttpSessionEvent;import javax.servlet.http.HttpSessionListener;import org.apache.log4j.Logger;import com.facelook.entity.User;public class SessionListener implements HttpSessionListener,ServletContextListener{private int count;private ServletContext servletContext = null;public SessionListener() {count = 0;}private Logger logger = Logger.getLogger(this.getClass());@Overridepublic void sessionCreated(HttpSessionEvent event) {count++;setContext(event);logger.info("***************the  http session is created...***************");}@Overridepublic void sessionDestroyed(HttpSessionEvent event) {//在session销毁的时候 把loginUserMap中保存的键值对清除User user = (User)event.getSession().getAttribute("loginUser");if(user!=null){Map<String, String> loginUserMap = (Map<String, String>)event.getSession().getServletContext().getAttribute("loginUserMap");loginUserMap.remove(user.getFuUserName());event.getSession().getServletContext().setAttribute("loginUserMap",loginUserMap);}count--;setContext(event);logger.info("***************the  http session is destroyed...***************");}public void setContext(HttpSessionEvent httpSessionEvent){httpSessionEvent.getSession().getServletContext().setAttribute("online", count);}@Overridepublic void contextDestroyed(ServletContextEvent servletcontextevent) {this.servletContext = null;logger.info("***************the  servlet context is destroyed...***************");}@Overridepublic void contextInitialized(ServletContextEvent servletcontextevent) {this.servletContext = servletcontextevent.getServletContext();logger.info("***************the  servlet context is initialized...***************");}}

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

[java] view plaincopyprint?

  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. }  

/** * 退出登录 * @return * @throws ServletException * @throws IOException */public String logout() throws ServletException, IOException{try {Map<String, String> loginUserMap = (Map<String, String>) super.getApplicationAttr(Constant.LOGIN_USER_MAP);User user = (User) super.getSessionAttr(Constant.LOGIN_USER);super.removeAttribute(Constant.LOGIN_USER_MAP);loginUserMap.remove(user.getFuUserName());super.setApplicationAttr(Constant.LOGIN_USER_MAP,loginUserMap);logger.info("退出登录成功!");} catch (Exception e) {e.printStackTrace();logger.error("退出登录失败: "+e.getMessage());}return INPUT;}/** * 在线用户管理 * @return */public String loginManager(){return SUCCESS;}/** * 强制退出其他用户 * @return */public String logoutOther(){try {String username = ServletActionContext.getRequest().getParameter("username");Map<String, String> loginUserMap = (Map<String, String>) super.getApplicationAttr(Constant.LOGIN_USER_MAP);if(username!=null && loginUserMap.containsKey(username)){loginUserMap.remove(username);super.setApplicationAttr(Constant.LOGIN_USER_MAP, loginUserMap);}} catch (Exception e) {e.printStackTrace();logger.info("强制退出失败: "+e.getMessage());}return null;}

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

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

[html] view plaincopyprint?

  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,String> map = (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.                         <a 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>  

<%@page import="java.util.Map"%><%@page import="java.util.Map.Entry"%><%@ page language="java" pageEncoding="UTF-8" %><%@ include file="/common/taglib.jsp" %><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>欢迎来到Facelook</title><%@ include file="/common/resource.jsp" %><script type="text/javascript">  <!--  //在刷新或关闭时调用的事件  $(window).bind('beforeunload',function(){       $.ajax({            url:"${ctx}/system/user/user!logout.action",            type:"post",            success:function(){                alert("您已退出登录");            }        });   });    function logout(username){        if(username=="${sessionScope.loginUser.fuUserName}"){            alert("不允许退出自己账号!");            return;        }        $.ajax({            url:"${ctx}/system/user/user!logoutOther.action?username="+username,            type:"post",            success:function(){                $("#tr"+username).hide();                var count = parseInt($("#count").html());                $("#count").html(count-1);                alert("退出成功!");            }        });    }  //--></script></head><body><%@ include file="/common/header.jsp" %><div id="main" class="wrap">    <%@ include file="/common/lefter.jsp" %>    <div class="righter">        <div class="main">            <h2>登录列表</h2>            <%            Map<String,String> map = (Map<String,String>)application.getAttribute("loginUserMap");            out.println("目前共有<font id='count'>"+map.size()+"</font>个用户在线!!");            %>            <table border="1" width="400">            <%for(Entry<String,String> m : map.entrySet()){%>                <tr id="tr<%=m.getKey()%>">                    <td>                        <%=m.getKey()%>                    </td>                    <td width="80">                        <a href="javascript:logout('<%=m.getKey()%>')">强制退出</a>                    </td>                </tr>                <%}%>            </table>        </div>    </div></div> <%@ include file="/common/footer.jsp" %><%@ include file="/common/message.jsp" %>                    </body></html>


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

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


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

1 0
原创粉丝点击