黄色网页视频 I 影音先锋日日狠狠久久 I 秋霞午夜毛片 I 秋霞一二三区 I 国产成人片无码视频 I 国产 精品 自在自线 I av免费观看网站 I 日本精品久久久久中文字幕5 I 91看视频 I 看全色黄大色黄女片18 I 精品不卡一区 I 亚洲最新精品 I 欧美 激情 在线 I 人妻少妇精品久久 I 国产99视频精品免费专区 I 欧美影院 I 欧美精品在欧美一区二区少妇 I av大片网站 I 国产精品黄色片 I 888久久 I 狠狠干最新 I 看看黄色一级片 I 黄色精品久久 I 三级av在线 I 69色综合 I 国产日韩欧美91 I 亚洲精品偷拍 I 激情小说亚洲图片 I 久久国产视频精品 I 国产综合精品一区二区三区 I 色婷婷国产 I 最新成人av在线 I 国产私拍精品 I 日韩成人影音 I 日日夜夜天天综合

第十八章 并發(fā)登錄人數(shù)控制——《跟我學(xué)Shiro》

系統(tǒng) 1896 0

?

目錄貼: ?跟我學(xué)Shiro目錄貼

?

在某些項(xiàng)目中可能會(huì)遇到如每個(gè)賬戶同時(shí)只能有一個(gè)人登錄或幾個(gè)人同時(shí)登錄,如果同時(shí)有多人登錄:要么不讓后者登錄;要么踢出前者登錄(強(qiáng)制退出)。比如 spring security 就直接提供了相應(yīng)的功能; Shiro 的話沒有提供默認(rèn)實(shí)現(xiàn),不過(guò)可以很容易的在 Shiro 中加入這個(gè)功能。

?

示例代碼基于《第十六章 ? 綜合實(shí)例》完成,通過(guò) Shiro Filter 機(jī)制擴(kuò)展 KickoutSessionControlFilter 完成。

?

首先來(lái)看看如何配置使用( spring-config-shiro.xml

??

kickoutSessionControlFilter 用于控制并發(fā)登錄人數(shù)的?

      <bean id="kickoutSessionControlFilter" 
class="com.github.zhangkaitao.shiro.chapter18.web.shiro.filter.KickoutSessionControlFilter">
    <property name="cacheManager" ref="cacheManager"/>
    <property name="sessionManager" ref="sessionManager"/>

    <property name="kickoutAfter" value="false"/>
    <property name="maxSession" value="2"/>
    <property name="kickoutUrl" value="/login?kickout=1"/>
</bean>?
    

cacheManager :使用 cacheManager 獲取相應(yīng)的 cache 來(lái)緩存用戶登錄的會(huì)話;用于保存用戶—會(huì)話之間的關(guān)系的;

sessionManager :用于根據(jù)會(huì)話 ID ,獲取會(huì)話進(jìn)行踢出操作的;

kickoutAfter :是否踢出后來(lái)登錄的,默認(rèn)是 false ;即后者登錄的用戶踢出前者登錄的用戶;

maxSession :同一個(gè)用戶最大的會(huì)話數(shù),默認(rèn) 1 ;比如 2 的意思是同一個(gè)用戶允許最多同時(shí)兩個(gè)人登錄;

kickoutUrl :被踢出后重定向到的地址;

?

shiroFilter 配置?

         <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/login"/>
        <property name="filters">
            <util:map>
                <entry key="authc" value-ref="formAuthenticationFilter"/>
                <entry key="sysUser" value-ref="sysUserFilter"/>
                <entry key="kickout" value-ref="kickoutSessionControlFilter"/>
            </util:map>
        </property>
        <property name="filterChainDefinitions">
            <value>
                /login = authc
                /logout = logout
                /authenticated = authc
                /** = kickout,user,sysUser
            </value>
        </property>
    </bean>?
    

此處配置除了登錄等之外的地址都走 kickout 攔截器進(jìn)行并發(fā)登錄控制。

?

測(cè)試

此處因?yàn)? maxSession=2 ,所以需要打開 3 個(gè)瀏覽器(需要不同的瀏覽器,如 IE Chrome Firefox ),分別訪問(wèn) http://localhost:8080/chapter18/ 進(jìn)行登錄;然后刷新第一次打開的瀏覽器,將會(huì)被強(qiáng)制退出,如顯示下圖:?

第十八章 并發(fā)登錄人數(shù)控制——《跟我學(xué)Shiro》

KickoutSessionControlFilter核心代碼: ?

      protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    Subject subject = getSubject(request, response);
    if(!subject.isAuthenticated() && !subject.isRemembered()) {
        //如果沒有登錄,直接進(jìn)行之后的流程
        return true;
    }

    Session session = subject.getSession();
    String username = (String) subject.getPrincipal();
    Serializable sessionId = session.getId();

    //TODO 同步控制
    Deque<Serializable> deque = cache.get(username);
    if(deque == null) {
        deque = new LinkedList<Serializable>();
        cache.put(username, deque);
    }

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

    //如果隊(duì)列里的sessionId數(shù)超出最大會(huì)話數(shù),開始踢人
    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) {
                //設(shè)置會(huì)話的kickout屬性表示踢出了
                kickoutSession.setAttribute("kickout", true);
            }
        } catch (Exception e) {//ignore exception
        }
    }

    //如果被踢出了,直接退出,重定向到踢出后的地址
    if (session.getAttribute("kickout") != null) {
        //會(huì)話被踢出了
        try {
            subject.logout();
        } catch (Exception e) { //ignore
        }
        saveRequest(request);
        WebUtils.issueRedirect(request, response, kickoutUrl);
        return false;
    }
    return true;
}?
    

此處使用了 Cache 緩存用戶名—會(huì)話 id 之間的關(guān)系;如果量比較大可以考慮如持久化到數(shù)據(jù)庫(kù) / 其他帶持久化的 Cache 中;另外此處沒有并發(fā)控制的同步實(shí)現(xiàn),可以考慮根據(jù)用戶名獲取鎖來(lái)控制,減少鎖的粒度。

?

另外可參考 JavaEE 項(xiàng)目開發(fā)腳手架,其提供了后臺(tái)踢出用戶的功能:

https://github.com/zhangkaitao/es/blob/master/web/src/main/java/com/sishuok/es/sys/user/web/controller/UserOnlineController.java ?

? ??

?

?

示例源代碼: https://github.com/zhangkaitao/shiro-example ;可加群 231889722?探討Spring/Shiro技術(shù)。

? ? ? ??

??

第十八章 并發(fā)登錄人數(shù)控制——《跟我學(xué)Shiro》


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論