使用httpsessionlistener实现禁止账号多点登录功能

有时候我们想禁止一个账号在多个地方登录,以防止账号被滥用的情况,那么如果实现这种效果呢?一般有两种实现效果,一种是如果账号已登录,则后续不能再登录,除非此账号已超时或注销登录;第二种是在登录时提示该账号已在其他地方登录,是否继续登录。

第一种实现不太友好,如果这个账号一直不退出,则其他人一直无法登录,下面展示第2种的实现方式。

– 实现原理:

通过实现HttpSessionListener的HttpSessionAttributeListener接口和HttpSessionAttributeListener的attributeAdded接口来更新在线用户列表,然后登录时检查此在线用户列表,如果列表中有当前登录用户则提示“该账号已在其他地方登录,是否继续”。

实现逻辑:

  1. 通过监听器维护一个在线用户列表容器,容器存放用户的LoginName, HttpSession键值对。

  2. 通过实现HttpSessionAttributeListener的attributeAdded接口来更新在线用户列表,只要向Session中添加属性便会调用此方法。
    一般在登录时会向Session中存放一个用户信息,在attributeAdded中判断如果存放到session中的属性为用户登录的属性名,则将该session放入在线用户列表容器中。
    此处不使用HttpSessionListener的sessionCreated的原因是,浏览器打开时会产生一个session,但是当用户登录时如果创建session不加true参数不会产生一个新的session,此时不关闭浏览器,注销当前用户再登录下一个用户,不会产生一个新的session,因为这时不会调用sessionCreated方法。此处和用户的登录代码有一定关系。

  3. 通过sessionDestroyed方法实现在线用户列表的移除,即当用户注销或者session超时便会调用此方法。

  4. 编写检查登录账号是否已经在在线用户列表容器中的接口,注意此处要先验证账号密码是否正确,如果账号密码不正确则继续走接下去的登录逻辑,不提示账号密码错误,因为此处验证账号密码只是做为是否提示账号在他处登录的条件。

  5. 在前端登录时调用判断账号是否可以多点登录的接口。

PS:此代码为单机版实现,如果为集群环境,请将在线用户列表放入Redis或其他地方进行维护。

1、编写在线用户列表容器类

package com.gsafety.iams.listener;

import java.util.Hashtable;
import java.util.Map;

import javax.servlet.http.HttpSession;

public class OnlineUserList {
	
	private static Map<String, HttpSession> onlineUsers =  new Hashtable<String, HttpSession>();

	private OnlineUserList() {
		super();
	}

	public static synchronized void put(String loginName, HttpSession session) {
		onlineUsers.put(loginName, session);
	}
	
	public static synchronized HttpSession get(String loginName) {
		return onlineUsers.get(loginName);
	}
	
	public static synchronized boolean containsKey(String loginName) {
		return onlineUsers.containsKey(loginName);
	}
	
	public static synchronized void remove(String loginName) {
		onlineUsers.remove(loginName);
	}
}

2.、 实现HttpSessionListener和HttpSessionAttributeListener

package com.gsafety.iams.listener;

import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import org.apache.log4j.Logger;

import com.gsafety.cloudframework.config.po.Configuration;
import com.gsafety.cloudframework.config.util.ConfigCacheUtil;
import com.gsafety.cloudframework.runtime.vo.SessionBean;

public class UserLoginSessionListener implements HttpSessionListener, HttpSessionAttributeListener {

	private static final Logger LOG = Logger.getLogger(UserLoginSessionListener.class);
	private static final String SESSION_BEAN = "session_bean";
	
	
	@Override
	public void sessionCreated(HttpSessionEvent e) {
	}

	@Override
	public void sessionDestroyed(HttpSessionEvent e) {
		HttpSession session = e.getSession();
		String loginName = getLoginName(session);
		OnlineUserList.remove(loginName);
		LOG.info(String.format("【%s】已注销..", loginName));
	}

	private String getLoginName(HttpSession session) {
		SessionBean sessionBean = (SessionBean) session.getAttribute("session_bean");
		if (sessionBean == null)
			return null;
		return sessionBean.getUser().getLoginName();
	}

	/**
	 * 通过session属性的变化来判断用户是否已在其他地方登录
	 */
	@Override
	public void attributeAdded(HttpSessionBindingEvent e) {
		String attrName = e.getName();
		if (SESSION_BEAN.equals(attrName)) {
			HttpSession session = e.getSession();
			String loginName = getLoginName(session);
			
			//限制多点登录,并且用户已在其他设备登录
			if (OnlineUserList.containsKey(loginName) && isSessionLimit()) {
				HttpSession ss = OnlineUserList.get(loginName);
				//将session置为失效
				ss.invalidate();
			}
			
			OnlineUserList.put(loginName, session);
			LOG.info(String.format("登录成功,用户:【%s】", loginName));
		}
	}
	
	private boolean isSessionLimit() {
		Boolean sessionLimit = false;	
		Configuration config = ConfigCacheUtil.getConf("system.session.limit");	
		if (config != null) {
			sessionLimit = Boolean.valueOf(config.getValue());
		}
		return sessionLimit;
	}

	@Override
	public void attributeRemoved(HttpSessionBindingEvent e) {
	}

	@Override
	public void attributeReplaced(HttpSessionBindingEvent e) {
	}
	
}

3、 编写action

package com.gsafety.iams.actions;

import org.apache.commons.lang.StringUtils;
import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.ParentPackage;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;

import com.gsafety.cloudframework.common.base.util.ActionUtil;
import com.gsafety.cloudframework.common.base.util.encrypt.DESCoder;
import com.gsafety.cloudframework.common.base.util.encrypt.MD5Digester;
import com.gsafety.cloudframework.config.po.Configuration;
import com.gsafety.cloudframework.config.util.ConfigCacheUtil;
import com.gsafety.cloudframework.user.facade.EmsUserFacade;
import com.gsafety.cloudframework.user.po.EmsUser;
import com.gsafety.iams.listener.OnlineUserList;
import com.opensymphony.xwork2.ActionSupport;

@ParentPackage("json-default")
@Namespace("/sys/user")
@Results(value = { @Result(name = "checkCanMultiLogin", type = "json", params= {"root", "canMultiLogin"}) })
public class LoginControlAction extends ActionSupport {

	private static final long serialVersionUID = 1L;
	
	private String loginName;	//账号
	private String password;	//密码
	
	private boolean canMultiLogin;
	
	/**
	 * 是否允许多处登录
	 * @return
	 */
	public String checkCanMultiLogin() {
		canMultiLogin = true;		//默认为允许
		if (checkUserPassword()) {
			//已存在登录用户,并且系统禁止多点登录
			if (OnlineUserList.containsKey(loginName) && isSessionLimit()) {
				canMultiLogin = false;;
			}
		}
		return "checkCanMultiLogin";
	}
	
	/**
	 * 是否禁止session登录,从配置中读取。
	 * @return
	 */
	private boolean isSessionLimit() {
		Boolean sessionLimit = false;	
		//此处从配置中读取是否控制多点登录,true为控制,false不控制
		//......代码省略,自己实现
		return sessionLimit;
	}
	
	/**
	 * 验证用户账号密码
	 * @return
	 */
	private boolean checkUserPassword() {
		//验证账号密码是否正确,代码省略
		//......
		return false;
	}


	public String getLoginName() {
		return loginName;
	}

	public void setLoginName(String loginName) {
		this.loginName = loginName;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public boolean isCanMultiLogin() {
		return canMultiLogin;
	}

	public void setCanMultiLogin(boolean canMultiLogin) {
		this.canMultiLogin = canMultiLogin;
	}


	
	
}


4、 在web.xml中配置监听器

  <listener>
  	<listener-class>com.gsafety.iams.listener.UserLoginSessionListener</listener-class>
  </listener>

5、 修改前端登录,下面是JS调用Action判断是否允许多点登录的方法

function checkCanMultiLogin(loginName, finalPass) {
				var canMultiLogin = false;
				jQuery.ajax({
						type: "POST",
						url: "${base}/sys/user/login-control!checkCanMultiLogin.do",
						data: {'loginName':loginName,'password':finalPass},
						async: false, 
						success: function(data) {
							canMultiLogin = data;
						}
				});
				return canMultiLogin;
			}

6.、在登录之前添加如下判断

//检查是否允许多点同时登录
				var canMultiLogin = checkCanMultiLogin(loginName, finalPass);
				if (!canMultiLogin || canMultiLogin == 'false') {
					if(!confirm("当前账号已在其他地方登录,是否继续")) {
						return false;
					}
				}
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值