JavaWeb-實現多ip、異地 同時登錄踢人下線

  • 所用知識點羅列:
cookie 、session、serverlet過濾器、serverlet監聽器,前提環境是基於Session實現的登錄功能
(Session中存放了登錄者的ip,userName等信息作已登錄標記)
  • 需要理解的基本概念
Session是基於cookie的,請求會話中,通過瀏覽器cookie攜帶的JsessionId
的sessionId號來找到服務器中相應的Session的.
如果沒有設置cookie的有效時間,一旦瀏覽器關閉,cookie就消失了,
其攜帶的sessionId也就丟失了,
這時即使服務器中的當前用戶的Session還未過期失效,依然存在,也無法找到了,基本等於是銷燬了.
  • 問題關鍵所在
正常使用Session實現的登錄時把登陸標記存放在Session中,在一次會話中有效。
Session是以會話爲隔離的,(其他瀏覽器或其他電腦也可以在打開一個會話),不同會話就可以創建同一用戶的不同session。
也就造成了服務器端可以有任意多個SessionId不同,但Session內容卻相同的Session,
也即是同一個用戶可以在多個瀏覽器登錄,無論是否是在同一臺電腦(ip)、同一個地區。
這也是我們要實現多ip登錄踢人下線功能要解決的問題。
  • 解決方案思路
1. 自己實現MySessionContext類搞一個map靜態成員變量以<SessionId,Session>的方式裝所有的Session,放服務器的運行內存中待用.
(其實使用serverletContext作爲容器也可以替代Session和這個自己實現的SessionContext)

2. 搞一個Session監聽器,監聽Session的創建和銷燬,在新的session創建時將其加入到上面自己創建的
MySessionContext的靜態成員變量map中,Session銷燬時(或者用戶註銷登錄時)把他的Session移除出map,
並用Session.invalidate()銷燬.

3. 用一個過濾器攔截過濾登錄請求,獲取登錄用戶的登錄標記,然後遍歷裝有Session的map,
對照是否有當前登錄用戶的Session存在,如果沒有就放行通過;
如果有,取出找到的session(也即是前一個登錄者生成的Session)移除出MySessionContext的map容器,
並銷燬這個Session(調用invalid()方法).此時前一個登錄者再刷新頁面時發現Session已經不存在了,配合先前做的Session過期過濾處理,就會和Session過期有一樣的效果——下線.

  • 參考代碼

登錄操作:

//獲取登錄ip地址
String ip = request.getRemoteAddr();
//將登錄者的ip放入到session中
request.getSession().setAttribute(ESessionKey.LoginIP.key, ip);
request.getSession().setAttribute(ESessionKey.UserId.key, user.getUserId());// 將用戶id存放到session中,作爲已登錄標記

MySessionContext實現

public class MySessionContext {
    private static HashMap<String,HttpSession> mymap = new HashMap<String,HttpSession>();
    public static synchronized void AddSession(HttpSession session) {
        if (session != null) {
            mymap.put(session.getId(), session);
        }
    }
    public static synchronized void DelSession(HttpSession session) {
        if (session != null) {
            HttpSession session2 = mymap.remove(session.getId());//移出session
            if(session2!=null){
                session2.invalidate();//將從sessionContext中移出的Session失效 --相當於清除當前Session對應的登錄用戶
            }
        }
    }
    public static synchronized HttpSession getSession(String session_id) {
        if (session_id == null)
            return null;
        return (HttpSession)mymap.get(session_id);
    }

    public static HashMap<String, HttpSession> getMymap() {
        return mymap;
    }
}

Session監聽器

public class SessionCounter implements HttpSessionListener {   

    private static int activeSessions = 0;   

    public void sessionCreated(HttpSessionEvent se) { 
        MySessionContext.AddSession(se.getSession());
        activeSessions++; 
        System.out.println("++++++++玩家上線了++++++++");
    }   

    public void sessionDestroyed(HttpSessionEvent se) {  
        if(activeSessions > 0)   
            activeSessions--;
        HttpSession session = se.getSession();
        MySessionContext.DelSession(session);
    }   

    public static int getActiveSessions() {   
        return activeSessions;   
    }   
}   

踢人下線過濾器核心代碼

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request =  (HttpServletRequest)req;
        HttpServletResponse response =  (HttpServletResponse)resp;
        String localIp = request.getRemoteAddr();//獲取本地ip
        HttpSession session = null;
        String user_id = (String)request.getParameter("userId"); //登錄請求時填寫的 
        if(StringUtils.isNotBlank(user_id))
        {
            session = getLoginedUserSession(user_id);
        }
        String loginedIp = null;
        if(session!=null)
        {
            loginedIp = (String)session.getAttribute(ESessionKey.LoginIP.key);//獲取已登錄者ip(如果有)
        }
        if(StringUtils.isNotBlank(loginedIp) && !localIp.equals(loginedIp)){
            MySessionContext.DelSession(session);//踢人--找到並銷燬Session
            request.setAttribute("msg", "您的賬號在其它ip登錄,您被踢下線了!");
            request.getRequestDispatcher("/login.jsp").forward(request, response);
        }else{
            chain.doFilter(request, response);//放行
        }
    }

Session過期過濾器 核心代碼

@Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        System.out.println("過濾請求...");

        HttpServletRequest request =  (HttpServletRequest)req;
        HttpServletResponse response =  (HttpServletResponse)resp;
        //session中獲取用戶名信息  
        String userId = (String)request.getSession().getAttribute("userId");  
        String admin = (String)request.getSession().getAttribute("admin");  

        //普通用戶登錄過濾,如果用戶名爲空說明帶有登錄標記的Session過期了
        if (userId==null||"".equals(userId.toString()) ) { 
            //超級管理員過濾
            if(admin==null||"".equals(admin.toString())){
               response.sendRedirect(request.getContextPath()+ADMIN_URL);
               return ;
            }

            //如果普通用戶和超級管理員都沒有登陸內容,說明登錄過期
            System.out.println("登錄過期,請重新登錄!");
            response.sendRedirect(request.getContextPath()+LOGIN_URL);
            PrintWriter printWriter = response.getWriter();
            String relogin = "登錄過期,請重新登錄!";
            printWriter.write(relogin);
            printWriter.flush();
            printWriter.close();
            return ;
        }
        //過濾通過,放行
        chain.doFilter(request, response);
        System.out.println("過濾響應!");
    }

web.xml配置

 <!-- 登錄踢人過濾器 -->
  <filter>
    <filter-name>TickFronterFilter</filter-name>
    <filter-class>com.fengyun.web.filter.TickFronterFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>TickFronterFilter</filter-name>
    <url-pattern>/login.html</url-pattern>
  </filter-mapping>
    <!-- session監聽器 -->
   <listener>
    <listener-class>   
        com.fengyun.web.filter.SessionCounter   
    </listener-class>
  </listener>
    <!-- session過期過濾器 -->
  <filter>
    <filter-name>Loginfilter</filter-name>
    <filter-class>com.fengyun.web.filter.LoginOverdueFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>Loginfilter</filter-name>
    <url-pattern>/material/*</url-pattern>
   ...等等需要過濾的url地址...當然可以使用通配方式寫(此處不詳述)
    <url-pattern>/operate_editeCompact.html</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>ERROR</dispatcher>
  </filter-mapping>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章