目前web项目中,很多情况都是可以让同一个账户信息在不同的登录入口登录这次,这样子就不那么美好了。
推荐阅读:
现在有两种解决方案:
1、将用户的登录信息用一个标志位的字段保存起来,每次登录成功就标记1,注销登录就标记为0,当标记为1的时候不允许别人登录。
2、将用户的登录信息保存在application内置作用域内, 然后利用session监听器监听每一个登录用户的登录情况。
很显然,第一种方式 每次登录 都需要操作数据库,多了一些不必要的性能开销,而且在登录状态下 万一突然电脑关闭了,那就永远都不能登录了,可用性比较低。
但是第二种方式就不一样了,可操作性强,很方便维护所有在线用户的信息。
接下来 主要介绍第二种方式的具体实现:
1、在处理登录的login方法中,先查询数据库验证下该用户是否存在,如果存在 判断该登录账户是否已经锁定了, 然后从application内置作用域对象中取出所有的登录信息,查看该username账户是否已经登录,如果登录了,就友好提示下,反之表示可以登录,将该登录信息以键值对的方式保存在application中。
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
//没有使用零配置前 每个访问的方法都要加上@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中移除。表示该用户已经下线了。
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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中配置如下:
1
2
3
4
|
<!-- session listener --> <listener> <listener- class >com.facelook.util.SessionListener</listener- class > </listener> |
3、另外,还有一个问题,如果说登录的用户突然关闭了浏览器或者页面而没有点击退出按钮。那么可以利用beforeunload 事件,在浏览器刷新或者关闭的时候触发。
1
2
3
4
5
6
7
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对象,然后将在线人员总数统计出来存放进去;
具体代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
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()); @Override public void sessionCreated(HttpSessionEvent event) { count++; setContext(event); logger.info( "***************the http session is created...***************" ); } @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); } count--; setContext(event); logger.info( "***************the http session is destroyed...***************" ); } public void setContext(HttpSessionEvent httpSessionEvent){ httpSessionEvent.getSession().getServletContext().setAttribute( "online" , count); } @Override public void contextDestroyed(ServletContextEvent servletcontextevent) { this .servletContext = null ; logger.info( "***************the servlet context is destroyed...***************" ); } @Override public void contextInitialized(ServletContextEvent servletcontextevent) { this .servletContext = servletcontextevent.getServletContext(); logger.info( "***************the servlet context is initialized...***************" ); } } |
2、在UserAction中创建管理在线用户的模块的方法,并且支持强制退出的功能;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
/** * 退出登录 * @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、在管理页面加载在线用户的列表;
对应的方法定义完毕之后,然后再在对应的管理页面添加在线列表,具体如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
<% @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> |
好了启动部署项目,然后启动服务,进入在线用户管理模块,简单效果如下图:
需要注意的是:当前登录用户 不允许强制退出自己的登录信息。
这样子,基本上可以实现防止多用户登录的案例了!
以上所述是小编给大家介绍的Java Web开发防止多用户重复登录的完美解决方案,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对服务器之家网站的支持!
原文链接:http://blog.csdn.net/chenghui0317/article/details/9373345