用戶強制一臺設備登錄,其他設備登出

一. 前言

 在處理項目登錄問題的時候,爲了賬號的安全性以及信息的同步性,有時我們需要做到同一個賬戶只允許在一處地方登錄,如果一個賬戶在一個處地方登錄之後,之後在另一個地方也使用同一個賬戶登錄,則前一個登錄的賬戶就強制下線;
做到這種效果的方式有很多種,比如使用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等攔截機制的話,攔截過濾會更加方便;

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章