1. 程式人生 > >SpringMvc實現一個賬號只能在一個地方登陸,其他地方強制下線

SpringMvc實現一個賬號只能在一個地方登陸,其他地方強制下線

一. 前言

  1.  在處理專案登入問題的時候,為了賬號的安全性以及資訊的同步性,有時我們需要做到同一個賬戶只允許在一處地方登入,如果一個賬戶在一個處地方登入之後,之後在另一個地方也使用同一個賬戶登入,則前一個登入的賬戶就強制下線;
  2. 做到這種效果的方式有很多種,比如使用redis、memcache等快取機制就能輕鬆實現分散式狀態下,控制賬戶登入的單一性;
  3. 本篇部落格主要講解的是在不用redis等快取機制的前提下,通過後臺的攔截過濾器去實現這種效果;

二. 實現思路 

  1. 建立一個全域性的類SessionSave,用來儲存對應的每一個賬戶登入的sessionId;
  2. 在使用者登入的時候,根據賬戶名獲取是否已經儲存了sessionId,如果存在,則刪除原來的那個sessionId,然後重新儲存當前的sessionId;如果不存在,則直接儲存當前的sessionId;同時將當前的賬戶資訊儲存到全域性的Session裡面;
  3. 在action請求的時候,使用攔截過濾器類獲取同步session快取是否有當前賬戶,如果沒有,則直接跳轉到登入介面;
  4. 如果有,則獲取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
	}
}

 四. 總結

  1. 不同的框架的登入方式、攔截方式等會有所不同,但實現的思路基本都是如此;
  2. 本篇部落格使用的是java中基礎的登入過濾器doFilter攔截,如果專案中有使用shiro等攔截機制的話,攔截過濾會更加方便;
  3. 實踐是檢驗認識真理性的唯一標準,多動手嘗試就知道其中的原理了;