Java Web开发防止多用户重复登录的完美解决方案

 更新时间:2016年11月08日 09:18:52   转载 作者:夜空中苦逼的程序员  
在web项目开发中,很多情况下都可以让同一个账号信息在不同的登录入口登录很多次,这样子做的不是很完善。一般解决这种情况有两种解决方案,小编呢主要以第二种方式给大家介绍具体的实现方法,对java web 防止多用户重复登录的解决方案感兴趣的朋友一起看看吧

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

推荐阅读:

Java 多用户登录限制的实现方法

现在有两种解决方案:

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

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

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

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

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

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

代码如下:

//没有使用零配置前 每个访问的方法都要加上@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中移除。表示该用户已经下线了。

代码如下:

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中配置如下:

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

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

//在刷新或关闭时调用的事件 
$(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对象,然后将在线人员总数统计出来存放进去;

具体代码如下:

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中创建管理在线用户的模块的方法,并且支持强制退出的功能;

/** * 退出登录 
* @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、在管理页面加载在线用户的列表;

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

<%@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开发防止多用户重复登录的完美解决方案,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

相关文章

  • Spring Boot详细打印启动时异常堆栈信息详析

    Spring Boot详细打印启动时异常堆栈信息详析

    这篇文章主要给大家介绍了关于Spring Boot详细打印启动时异常堆栈信息的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Spring Boot具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-10-10
  • java将m3u8格式转成视频文件的方法

    java将m3u8格式转成视频文件的方法

    这篇文章主要介绍了如何java将m3u8格式转成视频文件,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-03-03
  • java 制作验证码并进行验证实例详解

    java 制作验证码并进行验证实例详解

    这篇文章主要介绍了java 制作验证码并进行验证实例详解的相关资料,需要的朋友可以参考下
    2017-04-04
  • 详解Spring Cloud中Hystrix的请求合并

    详解Spring Cloud中Hystrix的请求合并

    这篇文章主要介绍了详解Spring Cloud中Hystrix的请求合并,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05
  • Java常用数字工具类 数字转汉字(1)

    Java常用数字工具类 数字转汉字(1)

    这篇文章主要为大家详细介绍了Java常用数字工具类,数字转汉字,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-05-05
  • 详解Java线程池和Executor原理的分析

    详解Java线程池和Executor原理的分析

    这篇文章主要介绍了详解Java线程池和Executor原理的分析的相关资料,这里提供实例及分析原理帮助大家理解这部分知识,需要的朋友可以参考下
    2017-07-07
  • SpringCloud Ribbon负载均衡实例解析

    SpringCloud Ribbon负载均衡实例解析

    这篇文章主要介绍了SpringCloud Ribbon负载均衡实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • 巧妙的利用Mongodb做地理空间查询

    巧妙的利用Mongodb做地理空间查询

    本篇文章将会以Mongodb为数据库,讲述如何在数据库层级进行定位查询。具有很好的参考价值,下面跟着小编一起来看下吧
    2017-02-02
  • 用java将GBK工程转为uft8的方法实例

    用java将GBK工程转为uft8的方法实例

    本篇文章主要介绍了用java将GBK工程转为uft8的方法实例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • Java中的匿名对象定义与用法实例分析

    Java中的匿名对象定义与用法实例分析

    这篇文章主要介绍了Java中的匿名对象定义与用法,结合实例形式分析了java匿名对象的概念、原理、定义与相关使用注意事项,需要的朋友可以参考下
    2019-08-08

最新评论