多账号登录控制
场景:java系统中用户账号登录实现控制,实现用户同时只能在一处登录
思路:
- 用户登录时添加用户的登录信息
- 用户退出时删除用户的登录信息
- 用户请求的session超时时,删除用户的登录信息
- 用户访问系统时,拦截请求查看是否与已保存的登录信息匹配
分析:
思路图
主要代码:
登录页面部分代码:
//判断当前账号是否已经登录
$.get('${ROOT}/platform/isLogon', function (r) {
//已登录,则增加提醒框,确认是否继续登录
if (r) {
confirm("当前账号已登录,继续登录会使其他登录退出,确认继续登录嘛?", function () {
// 登录处理
isProcessing = true;
$('#btnLogin').text('登录中......').prop('disabled', 'true');
$.post
(
'${ROOT}/platform/logon', params,
function (rsp, textStatus, jqXHR) {
//登录请求的处理逻辑
}
);
}, function () {
//取消继续登录,则返回false
return false;
});
//未登录则继续登录
} else {
// 登录处理
isProcessing = true;
$('#btnLogin').text('登录中......').prop('disabled', 'true');
$.post
(
'${ROOT}/platform/logon', params,
function (rsp, textStatus, jqXHR) {
//登录请求的处理逻辑
}
);
}
});
登录时后台代码:
@RequestMapping(path = "/platform/isLogon", method = RequestMethod.GET)
public boolean isLogon(HttpServletRequest req) throws Exception
{
IForm form = getFormWrapper(req);
String loginId = form.get("userName");
SystemService service = GEOFF.SPRING_CONTEXT.getBean(SystemService.class);
User user = service.isLogon(loginId);
if (user != null && loginUserMap.size() >0){
String id = loginUserMap.get(user.getId().toString());
if (StringUtils.isNotBlank(id)){
lg.info("当前用户:"+loginId+" 已登录系统,增加登录确认弹框...");
return true;
}
}
lg.info("当前用户:"+loginId+" 未登录系统,正常登录系统...");
return false;
}
@RequestMapping(path = "/platform/logon", method = RequestMethod.POST)
public Object logon(HttpServletRequest req) throws Exception
{
/**
**
**登录时账号,密码,验证码的校验逻辑
**
*/
HttpSession session = req.getSession();
User sessionUser = (User) session.getAttribute("SESSION_USER");
if (StringUtils.isNotBlank(session.getId()) && sessionUser != null) {
loginUserMap.put(sessionUser.getId().toString(), session.getId());
}else{
lg.error("进入保存用户登录信息到全局变量方法中,保存用户登录信息出错!!!");
}
Map rejson = new HashMap<>();
rejson.put("success", true);
rejson.put("token", token);
return JSON.toJSONString(rejson);
}
定义保存用户登录的全局变量:
import java.util.HashMap;
import java.util.Map;
public class Geoff
{
//=================================================================
// 全局常量
//=================================================================
/**
* 存储在线用户,控制用户账号登录
*/
public static Map loginUserMap = new HashMap<>();
}
用户访问系统时的校验:
package cn.geoff.framework.core;
import cn.geoff.framework.core.controller.BaseController;
import cn.geoff.framework.core.vo.InterfaceMessage;
import cn.geoff.framework.core.vo.Message;
import cn.geoff.framework.platform.vo.User;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import static cn.geoff.framework.core.FORP.loginUserMap;
/**
* @author: Geoff
* @create: 2020-07-21 14:45
* @description:
*/
public class LoginInitInterceptor extends HandlerInterceptorAdapter {
private static final Logger lg = LoggerFactory.getLogger(LoginInitInterceptor.class);
/**
* 拦截系统的请求,实现多账号登录控制
* @Author: Geoff
* @date: 2020/7/22 11:13
* @param: request
* @param response
* @param handler
* @return: boolean
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
lg.info("请求到达Interceptor拦截器,开始处理拦截逻辑...");
try {
//获取请求中的session和用户对象
HttpSession session = request.getSession();
User sessionUser = (User) session.getAttribute(FORP.SESSION_USER);
if (loginUserMap != null && loginUserMap.size() > 0 && sessionUser != null) {
String sessionID = loginUserMap.get(sessionUser.getId().toString());
//进行非空校验,如何请求session与全局信息中不一致,则清除缓存,并跳转到登录页面
if (StringUtils.isNotBlank(sessionID)) {
if (sessionID.equalsIgnoreCase(session.getId())) {
//两处sessionID一致则不做处理
lg.info("Interceptor拦截器拦截请求中的sessionID与全局登录Map中一致!!!");
return true;
}else{
//不一致则清除缓存
lg.info("Interceptor拦截器拦截请求中的sessionID与全局登录Map中【不】一致,"+sessionUser.getUserName()+"强制退出系统!");
handleSessionTimeout(request,response);
return false;
}
}
}
} catch (Exception e) {
lg.error("Interceptor拦截器拦截请求出错!!!");
}
return false;
}
private void handleSessionTimeout(HttpServletRequest req, HttpServletResponse resp) throws Exception {
lg.warn("Session已超时,重定向至登陆页面:{}", req.getRequestURL());
lg.debug("Referer:{}", req.getHeader("Referer"));
if (BaseController.isAjaxRequest(req)) {
InterfaceMessage message = new InterfaceMessage(401, "请求失败:无效的登录信息!");
BaseController.writeJsonResponse(message, resp);
} else {
Message message = new Message("您的账号已在其它地方登录,如非本人操作,请及时修改密码!");
message.setType(3);
req.setAttribute("msg", message);
req.setAttribute("actionURL", "/");
req.getRequestDispatcher("/WEB-INF/jsp/common/message.jsp").forward(req, resp);
}
}
}
对应的配置文件:
session超时后,清除用户的登录信息:
package cn.geoff.framework.core;
import cn.geoff.framework.core.util.Redis;
import cn.geoff.framework.platform.vo.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import static cn.geoff.framework.core.GEOFF.loginUserMap;
/**
* session监听器,监听session的创建与销毁
* @author: Geoff
* @create: 2020-07-21 09:42
* @description:
*/
public class SessionListener implements HttpSessionListener {
private static final Logger lg = LoggerFactory.getLogger(SessionListener.class);
/**
* 实现session接口,监听session创建的方法
* @Author: Geoff
* @date: 2020/7/21 9:43
* @param: httpSessionEvent
* @return: void
*/
@Override
public void sessionCreated(HttpSessionEvent event) {
lg.info("session监听器初始化加载......");
}
/**
* 实现session接口,监听session的销毁方法
* @Author: Geoff
* @date: 2020/7/21 9:44
* @param: httpSessionEvent
* @return: void
*/
@Override
public void sessionDestroyed(HttpSessionEvent event) {
HttpSession session = event.getSession();
//获取当前session中用户
User sessionUser = (User)session.getAttribute(GEOFF.SESSION_USER);
//如果用户不为空,销毁对应的session
if (sessionUser != null){
try {
//清除session中对用的用户信息
session.removeAttribute(GEOFF.SESSION_USER);
//获取当前的token
String workToken = (String)session.getAttribute("workToken");
//清除Redis中用户的登录信息
Redis.delete(workToken,null);
//清除整个session信息
session.invalidate();
}catch (Exception e){
lg.error(sessionUser.getUserName()+":用户对应session超时后,销毁session信息出错!");
}finally {
//清除已保存的用户登录信息
loginUserMap.remove(sessionUser.getId().toString());
}
}
}
}
对应配置文件:
cn.geoff.framework.core.SessionListener
用户退出系统时:
/**
* 退出系统
* @param id 用户ID
* @param req 请求参数
*/
@RequestMapping("/platform/logout/{id}")
public ModelAndView logout(@PathVariable Long id, HttpServletRequest req)
{
lg.info("退出系统:userId[{}]", id);
// 清除用户权限缓冲
User user = getSessionUser(req);
//清除session中用户信息,并将session初始
req.getSession().removeAttribute(GEOFF.SESSION_USER);
req.getSession().invalidate();
//清除已保存的用户登录信息
loginUserMap.remove(user.getId().toString());
// 返回首页
return new ModelAndView("redirect:/");
}