一. 前言
在處理項目登錄問題的時候,爲了賬號的安全性以及信息的同步性,有時我們需要做到同一個賬戶只允許在一處地方登錄,如果一個賬戶在一個處地方登錄之後,之後在另一個地方也使用同一個賬戶登錄,則前一個登錄的賬戶就強制下線;
做到這種效果的方式有很多種,比如使用redis、memcache等緩存機制就能輕鬆實現分佈式狀態下,控制賬戶登錄的單一性;
本篇博客主要講解的是在不用redis等緩存機制的前提下,通過後臺的攔截過濾器去實現這種效果;
二. 實現思路
創建一個全局的類SessionSave,用來存儲對應的每一個賬戶登錄的sessionId;
在用戶登錄的時候,根據賬戶名獲取是否已經存儲了sessionId,如果存在,則刪除原來的那個sessionId,然後重新保存當前的sessionId;如果不存在,則直接保存當前的sessionId;同時將當前的賬戶信息保存到全局的Session裏面;
在action請求的時候,使用攔截過濾器類獲取同步session緩存是否有當前賬戶,如果沒有,則直接跳轉到登錄界面;
如果有,則獲取SessionSave裏面存儲的對應賬戶的sessionId和獲取當前的sessionId,用SessionSave獲取的sessionId和當前的sessionId做對比,如果相等,則說明賬戶在同一個地方登錄,直接放行action請求;如果不相等,則說明用戶已經在另外一個地方登錄,當前登錄將被強制下線,action請求被攔截,直接跳轉回到登錄界面;
三. 實現代碼
創建一個全局的SessionSave類,用來存儲全局的SessionId靜態變量:
public class SessionSave {
private static Map<String, String> SessionIdSave = new HashMap<String,String>();
public static Map<String, String> getSessionIdSave() {
return SessionIdSave;
}
public static void setSessionIdSave(Map<String, String> sessionIdSave) {
SessionIdSave = sessionIdSave;
}
}
賬戶登錄的時候保存最新的sessionId,同時,將當前的賬戶信息保存到全局的Session裏面:
@ResponseBody
@RequestMapping("login")
public Map<String, Object> login(HttpServletRequest request, String username) {
//這裏忽略其他的登錄判斷
log.info("用戶名" + username);
HttpSession session = request.getSession(true);
User user = userService.getUserMsg(userName);
// 登錄成功,保存當前用戶登錄的sessionId
String sessionID = request.getRequestedSessionId();
String userName = user.getUserName();
if (!SessionSave.getSessionIdSave().containsKey(userName)) {
SessionSave.getSessionIdSave().put(userName, sessionID);
}else if(SessionSave.getSessionIdSave().containsKey(userName)&&!sessionID.equals(SessionSave.getSessionIdSave().get(userName))){
SessionSave.getSessionIdSave().remove(userName);
SessionSave.getSessionIdSave().put(userName, sessionID);
}
Map<String, Object> map = new HashMap<>();
session.setAttribute("userMsg", user);
map.put("result", "success");
}
用戶請求action的時候,使用攔截器過濾session中的sessionId:
public class LoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
}
@SuppressWarnings("deprecation")
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse servletResponse = (HttpServletResponse) response;
HttpServletRequest servletRequest = (HttpServletRequest) request;
//獲取session
HttpSession session = servletRequest.getSession();
//獲得用戶請求的URI
String path = servletRequest.getRequestURI();
//獲取session的用戶信息
User user = (User) session.getAttribute("userMsg");
//如果是登錄界面直接放行
if (path.indexOf("login.do") > -1) {
chain.doFilter(servletRequest, servletResponse);
return;
}
//如果用戶信息爲空,表明session已經過期或者已經被清空,則跳轉到登陸頁面
if (user == null) {
servletResponse.sendRedirect(servletRequest.getContextPath() + "/login.do");
} else {
String sessionId = SessionSave.getSessionIdSave().get(user.getUsername());//獲取全局類SessionSave保存賬戶的靜態sessionId
String currentSessionId = session.getId();//獲取當前的sessionId
if (!currentSessionId.equals(sessionId)) {//如果兩個sessionId不等,則當前賬戶強制下線,需要重新登錄
servletResponse.sendRedirect(servletRequest.getContextPath() + "/login.do");
}else {// 如果是同一賬戶session則放行請求
chain.doFilter(servletRequest, servletResponse);
}
}
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
}
四. 總結
不同的框架的登錄方式、攔截方式等會有所不同,但實現的思路基本都是如此;
本篇博客使用的是java中基礎的登錄過濾器doFilter攔截,如果項目中有使用shiro等攔截機制的話,攔截過濾會更加方便;