多賬號登入控制

昊狼發表於2020-07-22

多賬號登入控制

場景:java系統中使用者賬號登入實現控制,實現使用者同時只能在一處登入

思路

  1. 使用者登入時新增使用者的登入資訊
  2. 使用者退出時刪除使用者的登入資訊
  3. 使用者請求的session超時時,刪除使用者的登入資訊
  4. 使用者訪問系統時,攔截請求檢視是否與已儲存的登入資訊匹配

分析

思路圖

主要程式碼:

登入頁面部分程式碼:

//判斷當前賬號是否已經登入
$.get('${ROOT}/platform/isLogon', function (r) {
   //已登入,則增加提醒框,確認是否繼續登入
   if (r) {
      confirm("當前賬號已登入,繼續登入會使其他登入退出,確認繼續登入嘛?", function () {
         // 登入處理
         isProcessing = true;
         $('#btnLogin').text('登入中......').prop('disabled', 'true');
         $.post
         (
             '${ROOT}/platform/logon', params,
					function (rsp, textStatus, jqXHR) {
             		 //登入請求的處理邏輯
                    }
         );
      }, function () {
          //取消繼續登入,則返回false
         return false;
      });
      //未登入則繼續登入
   } else {

      // 登入處理
      isProcessing = true;
      $('#btnLogin').text('登入中......').prop('disabled', 'true');
      $.post
      (
              '${ROOT}/platform/logon', params,
					function (rsp, textStatus, jqXHR) {
             		 //登入請求的處理邏輯
                    }
      );

   }
});

登入時後臺程式碼:


	@RequestMapping(path = "/platform/isLogon", method = RequestMethod.GET)
	public boolean isLogon(HttpServletRequest req) throws Exception
	{
		IForm form = getFormWrapper(req);
		String loginId = form.get("userName");
		SystemService service = GEOFF.SPRING_CONTEXT.getBean(SystemService.class);
		User user = service.isLogon(loginId);
		if (user != null && loginUserMap.size() >0){
			String id = loginUserMap.get(user.getId().toString());
			if (StringUtils.isNotBlank(id)){
			    lg.info("當前使用者:"+loginId+" 已登入系統,增加登入確認彈框...");
				return true;
			}
		}
        lg.info("當前使用者:"+loginId+" 未登入系統,正常登入系統...");
		return false;
	}

@RequestMapping(path = "/platform/logon", method = RequestMethod.POST)
public Object logon(HttpServletRequest req) throws Exception
{
    /**
    **
    **登入時賬號,密碼,驗證碼的校驗邏輯
    **
    */
    HttpSession session = req.getSession();
        User sessionUser = (User) session.getAttribute("SESSION_USER");
        if (StringUtils.isNotBlank(session.getId()) && sessionUser != null) {
            loginUserMap.put(sessionUser.getId().toString(), session.getId());
        }else{
            lg.error("進入儲存使用者登入資訊到全域性變數方法中,儲存使用者登入資訊出錯!!!");
        }
    
    Map<String, Object> rejson = new HashMap<>();
		rejson.put("success", true);
		rejson.put("token", token);
	return JSON.toJSONString(rejson);
}

定義儲存使用者登入的全域性變數:

import java.util.HashMap;
import java.util.Map;


public class Geoff
{
   //=================================================================
   //    全域性常量
   //=================================================================

   /**
    * 儲存線上使用者,控制使用者賬號登入
    */
   public static Map<String, String> loginUserMap = new HashMap<>();
}

使用者訪問系統時的校驗:

package cn.geoff.framework.core;


import cn.geoff.framework.core.controller.BaseController;
import cn.geoff.framework.core.vo.InterfaceMessage;
import cn.geoff.framework.core.vo.Message;
import cn.geoff.framework.platform.vo.User;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;


import static cn.geoff.framework.core.FORP.loginUserMap;

/**
 * @author: Geoff
 * @create: 2020-07-21 14:45
 * @description:
 */
public class LoginInitInterceptor extends HandlerInterceptorAdapter {

    private static final Logger lg = LoggerFactory.getLogger(LoginInitInterceptor.class);

    /**
     * 攔截系統的請求,實現多賬號登入控制
     * @Author: Geoff
     * @date: 2020/7/22 11:13
     * @param: request
     * @param response
     * @param handler
     * @return: boolean
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        lg.info("請求到達Interceptor攔截器,開始處理攔截邏輯...");
        try {
            //獲取請求中的session和使用者物件
            HttpSession session = request.getSession();
            User sessionUser = (User) session.getAttribute(FORP.SESSION_USER);
            if (loginUserMap != null && loginUserMap.size() > 0 && sessionUser != null) {
                String sessionID = loginUserMap.get(sessionUser.getId().toString());
                //進行非空校驗,如何請求session與全域性資訊中不一致,則清除快取,並跳轉到登入頁面
                if (StringUtils.isNotBlank(sessionID)) {
                    if (sessionID.equalsIgnoreCase(session.getId())) {
                        //兩處sessionID一致則不做處理
                        lg.info("Interceptor攔截器攔截請求中的sessionID與全域性登入Map中一致!!!");
                        return true;
                    }else{
                        //不一致則清除快取
                        lg.info("Interceptor攔截器攔截請求中的sessionID與全域性登入Map中【不】一致,"+sessionUser.getUserName()+"強制退出系統!");
                        handleSessionTimeout(request,response);
                        return false;
                    }
                }
            }
        } catch (Exception e) {
            lg.error("Interceptor攔截器攔截請求出錯!!!");
        }
        return false;
    }

    private void handleSessionTimeout(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        lg.warn("Session已超時,重定向至登陸頁面:{}", req.getRequestURL());
        lg.debug("Referer:{}", req.getHeader("Referer"));
        if (BaseController.isAjaxRequest(req)) {
            InterfaceMessage message = new InterfaceMessage(401, "請求失敗:無效的登入資訊!");
            BaseController.writeJsonResponse(message, resp);
        } else {
            Message message = new Message("您的賬號已在其它地方登入,如非本人操作,請及時修改密碼!");
            message.setType(3);
            req.setAttribute("msg", message);
            req.setAttribute("actionURL", "/");
            req.getRequestDispatcher("/WEB-INF/jsp/common/message.jsp").forward(req, resp);
        }

    }

}
  

對應的配置檔案:
<!--多賬號登入控制攔截器-->
		<mvc:interceptor>
			<mvc:mapping path="/**"/>

			<!--排查登入介面攔截-->
			<mvc:exclude-mapping path="/"/>
			<mvc:exclude-mapping path="/platform/isLogon"/>
			<mvc:exclude-mapping path="/platform/logon"/>
			<mvc:exclude-mapping path="/platform/error/**"/>

			<!--排除靜態資源請求-->
			<mvc:exclude-mapping path="/favicon.ico"/>
			<mvc:exclude-mapping path="/js/**"/>
			<mvc:exclude-mapping path="/css/**"/>
			<mvc:exclude-mapping path="/fonts/**"/>
			<mvc:exclude-mapping path="/image/**"/>
			<mvc:exclude-mapping path="/images/**"/>
			<mvc:exclude-mapping path="/app/**"/>
			<mvc:exclude-mapping path="/disk-file/**"/>
			<mvc:exclude-mapping path="/sound/**"/>
			<mvc:exclude-mapping path="/geoff/**"/>

			<bean class="cn.geoff.framework.core.LoginInitInterceptor"/>
		</mvc:interceptor>

session超時後,清除使用者的登入資訊:

package cn.geoff.framework.core;

import cn.geoff.framework.core.util.Redis;
import cn.geoff.framework.platform.vo.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import static cn.geoff.framework.core.GEOFF.loginUserMap;

/**
 * session監聽器,監聽session的建立與銷燬
 * @author: Geoff
 * @create: 2020-07-21 09:42
 * @description:
 */
public class SessionListener implements HttpSessionListener {

    private static final Logger lg = LoggerFactory.getLogger(SessionListener.class);

    /**
     * 實現session介面,監聽session建立的方法
     * @Author: Geoff
     * @date: 2020/7/21 9:43
     * @param: httpSessionEvent
     * @return: void
     */
    @Override
    public void sessionCreated(HttpSessionEvent event) {
        lg.info("session監聽器初始化載入......");
    }

    /**
     * 實現session介面,監聽session的銷燬方法
     * @Author: Geoff
     * @date: 2020/7/21 9:44
     * @param: httpSessionEvent
     * @return: void
     */
    @Override
    public void sessionDestroyed(HttpSessionEvent event) {
        HttpSession session = event.getSession();
        //獲取當前session中使用者
        User sessionUser = (User)session.getAttribute(GEOFF.SESSION_USER);
        //如果使用者不為空,銷燬對應的session
        if (sessionUser != null){
            try {
                //清除session中對用的使用者資訊
                session.removeAttribute(GEOFF.SESSION_USER);
                //獲取當前的token
                String workToken = (String)session.getAttribute("workToken");
                //清除Redis中使用者的登入資訊
                Redis.delete(workToken,null);
                //清除整個session資訊
                session.invalidate();
            }catch (Exception e){
                lg.error(sessionUser.getUserName()+":使用者對應session超時後,銷燬session資訊出錯!");
            }finally {
                //清除已儲存的使用者登入資訊
                loginUserMap.remove(sessionUser.getId().toString());
            }

        }

    }
}
  
對應配置檔案:
<!--多賬號控制session監聽器-->
<listener>
  <listener-class>cn.geoff.framework.core.SessionListener</listener-class>
</listener>

使用者退出系統時:

/**
 * 退出系統
 * @param id     使用者ID
 * @param req    請求引數
 */
@RequestMapping("/platform/logout/{id}")
public ModelAndView logout(@PathVariable Long id, HttpServletRequest req)
{
  lg.info("退出系統:userId[{}]", id);

// 清除使用者許可權緩衝
User user = getSessionUser(req);

 //清除session中使用者資訊,並將session初始
req.getSession().removeAttribute(GEOFF.SESSION_USER);
req.getSession().invalidate();

 //清除已儲存的使用者登入資訊
 loginUserMap.remove(user.getId().toString());
// 返回首頁
 return new ModelAndView("redirect:/");
}

相關文章