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

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

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

现在有两种解决方案:

    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>



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

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


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



没有更多推荐了,返回首页