單一用戶登陸

參考連接:
單一用戶登陸實現:https://www.cnblogs.com/leechenxiang/p/6171151.html
shiro攔截器介紹:http://jinnianshilongnian.iteye.com/blog/2025656
shiro全配置文件:http://www.cnblogs.com/leechenxiang/p/6136017.html

需求:

現在項目應用在web端和app端,一個用戶要麼登陸在web端要麼app端,並且不能重複登陸,如果重複登陸,新登陸的客戶端會踢掉已經登陸的客戶端用戶。即一個用戶只能同時在線一個客戶端

實現:

第一種:不適用框架,待補充
第二種:使用shiro框架來實現
如果使用shiro框架來實現,系統的認證授權模塊都由shiro框架來實現,可能需要重構權限框架
直接看如下連接:
https://www.cnblogs.com/leechenxiang/p/6171151.html

權限相關知識和shiro框架介紹見shiro權限
sso單點登陸見單點登陸
單一用戶登陸實現步驟如下:

  1. 導包,maven或者直接下載,直接百度
  2. 利用shiro框架搭建權限模塊,見上文
  3. 利用shiro新建自定義攔截器
    自定義攔截器可以繼承shiro相關攔截器,擴展AccessControlFilter,其繼承了PathMatchingFilter,並擴展了了兩個方法:
    isAccessAllowed:即是否允許訪問,返回true表示允許;
    onAccessDenied:表示isAccessAllowed方法返回爲false時,即用戶訪問拒絕時,是否自己處理,如果返回true表示自己不處理且繼續攔截器鏈執行,返回false表示自己已經處理了(比如重定向到另一個頁面)。
//導包省略
//自定義單一登陸shiro攔截器,可以繼承AccessControlFilter類
public class SessionKickoutControlFilter extends AccessControlFilter{
    //緩存,存入登陸用戶的sessionId,參數string爲kickout+用戶名+web/app,Deque<Serializable>爲sessionId集合(可能同時多個)
    private Cache<String, Deque<Serializable>> sessionKickoutCache;
    private boolean kickoutAfter = false;
    private int maxSession = 1;//最大登陸session人數
    private SessionManager sessionManager;
    private static final String keyPrefix = "kickout";

    @Autowired
    AdminService adminService;

    @Autowired
    UserLogService userLogService;

    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        //request獲得當前用戶  
        Subject subject = getSubject(request,response);

        if(!subject.isAuthenticated() && !subject.isRemembered()) {  
            //如果沒有登錄,返回true登陸即可
            return true;  
        }
        Session session = subject.getSession();  
        String username = (String) subject.getPrincipal();  
        Serializable sessionId = session.getId();  
        String client = null;
        String[] clients = request.getParameterValues("client");
        if(clients != null && clients.length > 0){
            client = "1".equals(clients[0]) ? "APP" : "WEB";
        }else {
            client = ShiroUtil.getLoginClient();
        }
        //需要同步緩存值
        synchronized(this.sessionKickoutCache){

            Deque<Serializable> deque = sessionKickoutCache.get(keyPrefix + client + username); 
            if(deque == null || deque.size() == 0) {  
                deque = new LinkedList<Serializable>();  
            }  

            //如果隊列裏沒有此sessionId,且用戶沒有被踢出;放入隊列  
            if(!deque.contains(sessionId) && session.getAttribute("kickout") == null) {  
                deque.push(sessionId);  
            }

            //如果隊列裏的sessionId數超出最大會話數,開始踢人  
            while(deque.size() > maxSession) {  
                Serializable kickoutSessionId = null;  
                if(kickoutAfter) { //如果踢出後者  
                    kickoutSessionId = deque.removeFirst();  
                } else { //否則踢出前者  
                    kickoutSessionId = deque.removeLast();  
                }  
                try {  
                    Session kickoutSession =  sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));  
                    if(kickoutSession != null) {  
                        //設置會話的kickout屬性表示踢出了  
                        kickoutSession.setAttribute("kickout", true);  
                    }  
                } catch (Exception e) {//ignore exception  
                }  
            } 
            sessionKickoutCache.put(keyPrefix + client  + username, deque);
        }
        //如果被踢出了,直接退出,重定向到踢出後的地址  
        if (session.getAttribute("kickout") != null && (Boolean)session.getAttribute("kickout")) {  
            session.setAttribute("kickout", false);
            //會話被踢出了  
            try {
                String loginIp = BaseUtil.getIpAddr((HttpServletRequest)request);
                userLogService.insertUserLog(ShiroUtil.getUserNo(),ShiroUtil.getUserName(), ShiroUtil.getLoginClient(), loginIp, UserLogService.USER_LOG_LOGOUT);
                subject.logout();  
            } catch (Exception e) { //ignore  
            }  
            saveRequest(request);  
            response.getWriter().write(JSONObject.toJSONString(adminService.kickoutLogin()));
            //如果踢出以前登陸用戶,可以讓他重定向kickoutUrl(登陸頁面)
            WebUtils.issueRedirect(request, response, kickoutUrl);  
            return false;  
        }  
        return true;  
    }
    //返回false不允許直接訪問,要進行攔截,返回true,則可以直接訪問,一般繼承這個類就是爲了不能直接訪問,要進行攔截所以要返回false
    @Override
    protected boolean isAccessAllowed(ServletRequest request,
            ServletResponse response, Object mappedValue) throws Exception {
        return false;
    }

    public void setSessionManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
    }

    public void setKickoutAfter(boolean kickoutAfter) {
        this.kickoutAfter = kickoutAfter;
    }

    public void setMaxSession(int maxSession) {
        this.maxSession = maxSession;
    }  

    public void setCacheManager(CacheManager cacheManager){
        this.sessionKickoutCache = cacheManager.getCache("sessionKickoutCache");
    }
}
  1. 配置SessionKickoutControlFilter攔截器
    第一種:未整合spring
    在shrio.ini文件中加入
[filters]  
kickout=cn.paic.rep.pare.shiro.SessionKickoutControlFilter
[urls]  
/**=kickout//表示對所有的連接都需要進行攔截判斷

第二種:spring整合(常用)
在application-shiro.xml文件中配置

    <!--第一步:在spring中注入自定義的攔截器
樓主定義了多個攔截器:先判斷單點登陸,然後單一用戶登陸,最後權限驗證,形成一個責任鏈
-->
    <bean id="ssoFilter" class="cn.paic.rep.pare.shiro.SSOFilter" />
    <bean id="_permissionsFilter" class="cn.paic.rep.pare.shiro._PermissionsFilter"></bean>

   <!--這是的單一用戶登陸配置項-->
    <bean id="sessionKickoutControlFilter" class="cn.paic.rep.pare.shiro.SessionKickoutControlFilter">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="sessionManager" ref="sessionManager"/>  
        <property name="kickoutAfter" value="false"/>  
        <property name="maxSession" value="1"/>  
<!--跳轉頁面    <property name="kickoutUrl" value="/login"/>        
 --> </bean>

    <!-- 配置shiro的過濾器工廠類,id- shiroFilter要和我們在web.xml中配置的過濾器一致 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">

        <!-- 調用我們配置的權限管理器 -->
        <property name="securityManager" ref="securityManager" />
        <!-- 配置我們的登錄請求地址 -->
        <!-- <property name="loginUrl" value="/login" /> -->
        <!-- 配置我們在登錄頁登錄成功後的跳轉地址,如果你訪問的是非/login地址,則跳到您訪問的地址 -->
        <!-- <property name="successUrl" value="/home" /> -->
        <!-- 如果您請求的資源不再您的權限範圍,則跳轉到/403請求地址 -->
        <!-- <property name="unauthorizedUrl" value="/api/admin/unauthorized" /> -->
        <!-- 權限配置 -->
        <!-- 第二步:以下爲配置自定義的攔截器 -->
        <property name="filters">
            <map>
                <entry key="ssoFilter" value-ref="ssoFilter" />
                <entry key="perms" value-ref="_permissionsFilter" />
                <entry key="kickout" value-ref="sessionKickoutControlFilter"/>
            </map>
        </property>
    </bean>

    <!-- 第三步:權限資源配置 -->  
    <bean id="filterChainDefinitionsService"    class="cn.paic.rep.pare.shiro.SimpleFilterChainDefinitionsService">  
        <property name="definitions">  
            <value>  
                <!--不需要登陸和攔截即可訪問-->
                 /api/file/getBbsImg/*=anon
                 <!--左邊對api下的請求都要進行攔截判斷-->            
                 /api/** =kickout,ssoFilter
            </value>  
        </property>  
發佈了63 篇原創文章 · 獲贊 17 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章