SpringMvc實現一個賬號只能在一個地方登陸,其他地方強制下線
阿新 • • 發佈:2018-12-06
一. 前言
- 在處理專案登入問題的時候,為了賬號的安全性以及資訊的同步性,有時我們需要做到同一個賬戶只允許在一處地方登入,如果一個賬戶在一個處地方登入之後,之後在另一個地方也使用同一個賬戶登入,則前一個登入的賬戶就強制下線;
- 做到這種效果的方式有很多種,比如使用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等攔截機制的話,攔截過濾會更加方便;
- 實踐是檢驗認識真理性的唯一標準,多動手嘗試就知道其中的原理了;