1. 程式人生 > >併發登入人數控制--Shiro系列(二)

併發登入人數控制--Shiro系列(二)

為了安全起見,同一個賬號理應同時只能在一臺裝置上登入,後面登入的踢出前面登入的。用Shiro可以輕鬆實現此功能。

shiro中sessionManager是專門作會話管理的,而sessinManager將會話儲存在sessionDAO中,如果不給sessionManager注入

sessionDAO,會話將是瞬時狀態,沒有被儲存起來,從sessionManager裡取session,是取不到的。

此例中sessionDAO注入了Ehcache快取,會話被儲存在Ehcache中,不知Ehcache為何物的,請自行查閱資料。

完整demo下載:http://download.csdn.net/detail/qq_33556185/9555720


shiro.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/beans   
    http://www.springframework.org/schema/beans/spring-beans-4.2.xsd   
    http://www.springframework.org/schema/tx   
    http://www.springframework.org/schema/tx/spring-tx-4.2.xsd  
    http://www.springframework.org/schema/context  
    http://www.springframework.org/schema/context/spring-context-4.2.xsd  
    http://www.springframework.org/schema/mvc  
    http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd">
     
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">  
        <property name="globalSessionTimeout" value="1800000"/>  
        <property name="deleteInvalidSessions" value="true"/>  
        <property name="sessionDAO" ref="sessionDAO"/>  
        <property name="sessionIdCookieEnabled" value="true"/>  
        <property name="sessionIdCookie" ref="sessionIdCookie"/>  
        <property name="sessionValidationSchedulerEnabled" value="true"/>
        <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
         <property name="cacheManager" ref="shiroEhcacheManager"/>
</bean>  
	   <!-- 會話DAO,sessionManager裡面的session需要儲存在會話Dao裡,沒有會話Dao,session是瞬時的,沒法從
     sessionManager裡面拿到session -->  
    <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">  
        <property name="sessionIdGenerator" ref="sessionIdGenerator"/>  
        <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>
    </bean> 
	  <!-- 快取管理器 -->
  <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">  
  <property name="cacheManagerConfigFile" value="classpath:ehcache.xml" />  
  </bean>
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">  
        <constructor-arg value="sid"/>
        <property name="httpOnly" value="true"/>
        <property name="maxAge" value="-1"/> 
</bean> 
 <!-- 會話ID生成器 -->
 <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"></bean>
<bean id="kickoutSessionControlFilter"  class="com.core.shiro.KickoutSessionControlFilter">  
    <property name="sessionManager" ref="sessionManager"/>  
    <property name="cacheManager" ref="shiroEhcacheManager"/>
    <property name="kickoutAfter" value="false"/>  
    <property name="maxSession" value="1"/>  
    <property name="kickoutUrl" value="/toLogin?kickout=1"/>  
</bean> 
      <bean id="logout" class="org.apache.shiro.web.filter.authc.LogoutFilter">
        <property name="redirectUrl" value="/toLogin" />
       </bean> 
          <!-- 會話驗證排程器 -->
    <bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler ">
        <property name="interval" value="1800000"/>
        <property name="sessionManager" ref="sessionManager"/>
    </bean>
	 <!-- Shiro Filter 攔截器相關配置 -->  
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
        <!-- securityManager -->  
        <property name="securityManager" ref="securityManager" />  
        <!-- 登入路徑 -->  
        <property name="loginUrl" value="/toLogin" />  
        <!-- 使用者訪問無許可權的連結時跳轉此頁面  -->  
        <property name="unauthorizedUrl" value="/unauthorizedUrl.jsp" />  
       
        <!-- 過濾鏈定義 -->  
         <property name="filters">  
         <map>  
             <entry key="kickout" value-ref="kickoutSessionControlFilter"/>  
         </map>  
     </property> 
        <property name="filterChainDefinitions">  
            <value>  
            	/loginin=kickout,anon
            	 /logout = logout
            	/toLogin=anon
            	/css/**=anon 
                /html/**=anon 
                /images/**=anon
                /js/**=anon 
                /upload/**=anon 
                <!-- /userList=roles[admin] -->
                /userList=kickout,authc,perms[/userList]
                /toRegister=kickout,authc,perms[/toRegister]
                /toDeleteUser=kickout,authc,perms[/toDeleteUser]
                /** = kickout,authc
             </value>  
        </property>  
    </bean>  
  
    <!-- securityManager -->  
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
        <property name="realm" ref="myRealm" />  
         <property name="sessionManager" ref="sessionManager"/>
         <property name="cacheManager" ref="shiroEhcacheManager"/>
    </bean>  
    <!-- 自定義Realm實現 --> 
    <bean id="myRealm" class="com.core.shiro.CustomRealm" />  
    
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
    
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">  
	   <property name="prefix" value="/"/>  
	   <property name="suffix" value=".jsp"></property>  
	</bean>
</beans>  
ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>  
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"  
    updateCheck="false" maxBytesLocalDisk="20G" maxBytesLocalOffHeap="50M">
      
    <diskStore path="java.io.tmpdir"/> <!-- 系統的預設臨時檔案路徑 -->
    <defaultCache eternal="false"   
        maxElementsInMemory="10000"  
        overflowToDisk="false"   
        timeToIdleSeconds="0"  
        timeToLiveSeconds="0"   
        memoryStoreEvictionPolicy="LFU" />  
   <!-- 
    eternal:快取中物件是否為永久的,如果是,超時設定將被忽略,物件從不過期。
    maxElementsInMemory:快取中允許建立的最大物件數
    overflowToDisk:記憶體不足時,是否啟用磁碟快取。
    timeToIdleSeconds:快取資料的鈍化時間,也就是在一個元素消亡之前,
            兩次訪問時間的最大時間間隔值,這隻能在元素不是永久駐留時有效,
 	 如果該值是 0 就意味著元素可以停頓無窮長的時間。
    timeToLiveSeconds:快取資料的生存時間,也就是一個元素從構建到消亡的最大時間間隔值,
           這隻能在元素不是永久駐留時有效,如果該值是0就意味著元素可以停頓無窮長的時間。
    memoryStoreEvictionPolicy:快取滿了之後的淘汰演算法。
    1 FIFO,先進先出
	2 LFU,最少被使用,快取的元素有一個hit屬性,hit值最小的將會被清出快取。
	3 LRU,最近最少使用的,快取的元素有一個時間戳,當快取容量滿了,而又需要騰出地方來快取新的元素的時候,那麼現有快取元素中時間戳離當前時間最遠的元素將被清出快取。
    -->
    <cache name="myCache"  eternal="false"   
        maxElementsInMemory="10000"  
        overflowToDisk="false"   
        timeToIdleSeconds="0"  
        timeToLiveSeconds="0"   
        memoryStoreEvictionPolicy="LFU" />  
    <cache name="shiro-activeSessionCache" eternal="false"
          maxElementsInMemory="10000"  
          overflowToDisk="true"
          timeToIdleSeconds="0"
          timeToLiveSeconds="0"/>
</ehcache> 
login.jsp的javascript:
<script type="text/javascript">
    function kickout(){
       var href=location.href;
       if(href.indexOf("kickout")>0){
           alert("您的賬號在另一臺裝置上登入,您被擠下線,若不是您本人操作,請立即修改密碼!");
       } 
    }
    window.onload=kickout(); 
</script>
KickoutSessionControlFilter
package com.core.shiro;

import java.io.Serializable;
import java.util.Deque;
import java.util.LinkedList;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
public class KickoutSessionControlFilter  extends AccessControlFilter{
	private String kickoutUrl; //踢出後到的地址
    private boolean kickoutAfter; //踢出之前登入的/之後登入的使用者 預設踢出之前登入的使用者
    private int maxSession; //同一個帳號最大會話數 預設1
    private SessionManager sessionManager;
    private Cache<String, Deque<Serializable>> cache;

    public void setKickoutUrl(String kickoutUrl) {
        this.kickoutUrl = kickoutUrl;
    }

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

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

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

    public void setCacheManager(CacheManager cacheManager) {
        this.cache = cacheManager.getCache("shiro-activeSessionCache");
    }
     /**
      * 是否允許訪問,返回true表示允許
      */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
    	return false;
    }
    /**
     * 表示訪問拒絕時是否自己處理,如果返回true表示自己不處理且繼續攔截器鏈執行,返回false表示自己已經處理了(比如重定向到另一個頁面)。
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        Subject subject = getSubject(request, response);
        if(!subject.isAuthenticated() && !subject.isRemembered()) {
            //如果沒有登入,直接進行之後的流程
            return true;
        }

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

        // 初始化使用者的佇列放到快取裡
        Deque<Serializable> deque = cache.get(username);
        if(deque == null) {
            deque = new LinkedList<Serializable>();
            cache.put(username, deque);
        }

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

        //如果佇列裡的sessionId數超出最大會話數,開始踢人
        while(deque.size() > maxSession) {
            Serializable kickoutSessionId = null;
            if(kickoutAfter) { //如果踢出後者
            	kickoutSessionId=deque.getFirst();
                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
            	e.printStackTrace();
            }
        }

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


相關推薦

併發登入人數控制--Shiro系列()

為了安全起見,同一個賬號理應同時只能在一臺裝置上登入,後面登入的踢出前面登入的。用Shiro可以輕鬆實現此功能。 shiro中sessionManager是專門作會話管理的,而sessinManager將會話儲存在sessionDAO中,如果不給sessionManager

第十八章 併發登入人數控制——《跟我學Shiro

在某些專案中可能會遇到如每個賬戶同時只能有一個人登入或幾個人同時登入,如果同時有多人登入:要麼不讓後者登入;要麼踢出前者登入(強制退出)。比如spring security就直接提供了相應的功能;Shiro的話沒有提供預設實現,不過可以很容易的在Shiro中加入這個功能

springboot整合shiro-線上人數以及併發登入人數控制(七)

專案中有時候會遇到統計當前線上人數的需求,也有這種情況當A 使用者在邯鄲地區登入 ,然後A使用者在北京地區再登入 ,要踢出邯鄲登入的狀態。如果使用者在北京重新登入,那麼又要踢出邯鄲的使用者,這樣反覆。 這樣保證了一個帳號只能同時一個人使用。那麼下面來講解一

第十八章 併發登入人數控制

在某些專案中可能會遇到如每個賬戶同時只能有一個人登入或幾個人同時登入,如果同時有多人登入:要麼不讓後者登入;要麼踢出前者登入(強制退出)。比如spring security就直接提供了相應的功能;Shiro的話沒有提供預設實現,不過可以很容易的在Shiro中加入這個功能。

IOS控制元件系列---優雅的UITableView的MVC模式設計,支援自定義下拉重新整理/上提載入更多檢視(含swift)

demo效果如下: 本小框架設計原則依舊按照之前的慣例: 1.擴充套件性好,程式碼不冗餘(整個重新整理的頭部與底部程式碼不超過300行)。 2.邏輯清晰。 3.回撥介面清晰。 4.移植性好。 對於擴充套件性本框架擴充套件點如下: 1.框架中的

C#:C#控制元件系列 (文字框類控制元件)

文字框類控制元件1. Label 控制元件1.1. 常用屬性:1.1.1. Text屬性:用來設定或返回標籤控制元件中顯示的文字資訊。1.1.2. AutoSize屬性:用來獲取或設定一個值,該值指示是否自動調整控制元件的大小以完整顯示其內容。——  取值為true時,控制元

Android自定義控制元件系列:自定義開關按鈕(一)

這一次我們將會實現一個完整純粹的自定義控制元件,而不是像之前的組合控制元件一樣,拿系統的控制元件來實現;計劃分為三部分:自定義控制元件的基本部分,和自定義控制元件的自定義屬性; 下面就開始第一部分的編寫,本次以一個定義的開關按鈕為例,下面就開始吧: 先看看效果,一個點選開

Android自定義控制元件系列()—icon+文字的多種效果實現

今天給大家帶來一個很簡單但是很常用的控制元件ButtonExtendM,在開發中我們經常會用到圖片加文字的組合控制元件,像這樣: 以上圖片都是從微信上擷取的。(暫時沒有找到icon在下,文字在上的例子) 下面我們通過一個控制元件來實現上下左右全部

springboot-許可權控制shiro

目錄 1. 場景描述 2. 解決方案 1. 場景描述 (1)最近有點小忙,公司真實專案內容有點小多以及不想只介紹理論,就使用springboot單獨部署了個shiro的demo專案,還是理論和實際項結合比較好理

成長記錄貼之springboot+shiro) {完成一個完整的許可權控制,詳細步驟}

       近一個月比較忙,公司接了一個新專案,領導要求用shiro進行安全管理,而且全公司只有我一個java,從專案搭建到具體介面全是一個人再弄。不過剛好前段時間大概學習了一下shiro的使用,還算順利。       &n

java併發系列(深入!!!理解synchronized,volatile)

一,synchronized詳解 這個關鍵字大家想必是相當熟悉了,它是一個比較重量級的鎖,主要有兩層含義,一個是互斥性,一個是可見性。三種用法:1,修飾普通方法2,修飾靜態方法3,修飾程式碼塊 這裡有一點需要注意,普通方法要拿到當前例項的鎖,靜態方法要拿到當前class物件的鎖。 重點來

web框架UI系列--MVC常用控制元件講解

  LinkExtention之@Html.AcionLink() 雲微平臺B/S開發框架 作用:返回包含指定操作的虛擬路徑的定位點元素(a 元素),返回型別:System.Web.Mvc.MvcHtmlString @Html.ActionLink("HtmlHelper-AcionLin

SpringBoot系列: SpringBoot Web專案中使用Shiro

==================================Shiro 的加深理解:==================================1. Shiro 和 Spring 系元件的對標, Shiro = Spring Security + Spring Session. 就是說 Shi

單點登入(十八)----cas4.2.x客戶端增加許可權控制shiro

我們在上面章節已經完成了cas4.2.x登入啟用mongodb的驗證方式。也完成了獲取管理員身份屬性現在需要做的就是給客戶端 cas client加上 許可權控制。許可權控制可以使用spring Security或者shiro。安全框架Shiro和Spring Securit

手摸手,帶你用vue擼後臺 系列(登入許可權篇)

前言 拖更有點嚴重,過了半個月才寫了第二篇教程。無奈自己是一個業務猿,每天被我司的產品虐的死去活來,之前又病了一下休息了幾天,大家見諒。 進入正題,做後臺專案區別於做其它的專案,許可權驗證與安全性是非常重要的,可以說是一個後臺專案一開始就必須考慮和搭建的基礎核心功能

ASP.NET AJAX入門系列(5):使用UpdatePanel控制元件(

{            if (String.IsNullOrEmpty(FirstNameTextBox.Text) ||               String.IsNullOrEmpty(LastNameTextBox.Text)) { return; }             int emplo

Android自定義控制元件系列八:詳解onMeasure()()--利用onMeasure測量來實現圖片拉伸永不變形,解決螢幕適配問題

        上一篇文章詳細講解了一下onMeasure/measure方法在Android自定義控制元件時的原理和作用,參看博文:Android自定義控制元件系列七:詳解onMeasure()方法中如何測量一個控制元件尺寸(一),今天就來真正實踐一下,讓這兩個方法大顯神威來幫我們搞定圖片的螢幕適配問題。

Android學習系列(): 儲存檔案到手機記憶體-模擬使用者登入儲存使用者資訊

今天要學習如何儲存檔案到手機記憶體中,具體思想如下: (1)建立登入的Activity,包含使用者名稱EditText,密碼EditText,記住密碼CheckBox 和登入按鈕Button。 (2)建立儲存和讀取檔案的業務類,以供activity入口類呼叫。 (3)建立登

Spring Boot系列(十五) 安全框架Apache Shiro)快取-基於Hazelcast的分散式快取

通常所說的“分散式”、“叢集服務”、“網格式記憶體資料”、“分散式快取“、“彈性可伸縮服務”這些非常牛逼高大上名詞拿到哪都是ITer裝逼的不二之選。在Javaer的世界,有這樣一個開源專案,只需要引入一個jar包、只需簡單的配置和編碼即可實現以上高階技能,他就是

Java併發程式設計系列()-synchronized同步鎖

synchronized基本用法 從一個簡單的例子入手 1 public class C1 { 2 private int count = 10; 3 4 public void foo() { 5 count--; 6 System.out.p