2017.6.30 用shiro實現並發登錄人數控制(實際項目中的實現)


之前的學習總結:http://www.cnblogs.com/lyh421/p/6698871.html

 

1.kickout功能描述

如果將配置文件中的kickout設置為true,則在另處再次登錄時,會將第一次登錄的用戶踢出。
 

2.kickout的實現

2.1 新建KickoutSessionControlFilter extends AccessControlFilter

詳細的方法實現,后面再來完成。類存放於公共module:base_project中。

 1 public class KickoutSessionControlFilter extends AccessControlFilter {
 2     @Override
 3     protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
 4         return false;
 5     }
 6 
 7     @Override
 8     protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
 9         return false;
10     }
11 }

 

2.2 配置spring-config-shiro.xml

這兩個文件配置在要使用kickout功能的module中。

(1)kickoutSessionControllerFilter

kickoutAfter:是否提出后來登錄的,默認為false,即后來登錄的踢出前者。

maxSession:同一個用戶的最大會話數,默認1,表示同一個用戶最多同時一個人登錄。

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

1 <!--並發登錄控制-->
2     <bean id="kickoutSessionControlFilter" class="***.common.filter.KickoutSessionControlFilter">
3         <property name="cacheManager" ref="springCacheManager"/>
4         <property name="kickoutAfter" value="false"/>
5         <property name="maxSession" value="1"/>
6         <property name="kickoutUrl" value="/login.do"/>
7     </bean>

 

(2)shiroFilter

此處配置什么時候走kickout 攔截器,進行並發登錄控制。這里攔截所有.jsp和.do的路徑。

 1 <bean id="AuthRequestFilter" class="com.baosight.aas.auth.filter.AuthRequestFilter"/>
 2     <!-- Shiro主過濾器本身功能十分強大,其強大之處就在於它支持任何基於URL路徑表達式的、自定義的過濾器的執行 -->
 3     <!-- Web應用中,Shiro可控制的Web請求必須經過Shiro主過濾器的攔截,Shiro對基於Spring的Web應用提供了完美的支持 -->
 4     <bean id="shiroFilter" class="com.baosight.aas.auth.filter.factory.ClientShiroFilterFactoryBean">
           //略
13         <property name="filters">
14             <util:map>
15                 <entry key="authc" value-ref="formAuthenticationFilter"/>
                   //略
19                 <entry key="kickout" value-ref="kickoutSessionControlFilter"/>
20             </util:map>
21         </property>
27         <property name="filterChainDefinitions">
28             <value>
                   //略48                 /**/*.jsp = forceLogout,authc,kickout 49                 /**/*.do = forceLogout,authc,kickout 50                 /** = forceLogout,authc
51             </value>
52         </property>
53     </bean>

2.3 ehcache.xml

注意,其他module在配置shiro的時候,都是使用的公共module:base_project中的ehcache.xml文件。在此文件中加上一段:

這里的名稱shiro-kickout-session在后面的kickoutController里要用到。

1 <cache name="shiro-kickout-session"
2            eternal="false"
3            timeToIdleSeconds="3600"
4            timeToLiveSeconds="0"
5            overflowToDisk="false"
6            statistics="true">
7     </cache>

 

2.4 實現KickoutSessionControlFilter

  1 package com.baosight.common.filter;
  2 
  3 import org.apache.shiro.cache.Cache;
  4 import org.apache.shiro.cache.CacheManager;
  5 import org.apache.shiro.session.Session;
  6 import org.apache.shiro.subject.Subject;
  7 import org.apache.shiro.web.filter.AccessControlFilter;
  8 import org.slf4j.Logger;
  9 import org.slf4j.LoggerFactory;
 10 import org.springframework.beans.factory.annotation.Value;
 11 
 12 import javax.servlet.ServletRequest;
 13 import javax.servlet.ServletResponse;
 14 import java.io.Serializable;
 15 import java.util.Deque;
 16 import java.util.LinkedList;
 17 
 18 /**
 19  * Created by liyuhui on 2017/4/12.
 20  */
 21 public class KickoutSessionControlFilter extends AccessControlFilter{
 22     private static final Logger LOGGER = LoggerFactory.getLogger(KickoutSessionControlFilter.class);
 23 
 24     @Value("${aas.kickout:false}")
 25     private String kickout;
 26 
 27     private String kickoutUrl;
 28     private boolean kickoutAfter = false;
 29     private int maxSession = 1;
 31     private Cache<String, Deque<Session>> cache;  32 
 33     public void setKickoutUrl(String kickoutUrl) {
 34         this.kickoutUrl = kickoutUrl;
 35     }
 36 
 37     public void setKickoutAfter(boolean kickoutAfter) {
 38         this.kickoutAfter = kickoutAfter;
 39     }
 40 
 41     public void setMaxSession(int maxSession) {
 42         this.maxSession = maxSession;
 43     }
 44 
 45     public void setCacheManager(CacheManager cacheManager) {
 46         this.cache = cacheManager.getCache("shiro-kickout-session");  47     }
 48 
 49     @Override
 50     protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
 51         return false;
 52     }
 53 
 54     @Override
 55     protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
 56         if(!"true".equals(kickout)){
 57             //如果不需要單用戶登錄的限制
 58             return true;
 59         }
 60 
 61         Subject subject = getSubject(request, response);
 62         if(!subject.isAuthenticated() && !subject.isRemembered()){
 63             //如果沒登錄,直接進行之后的流程
 64             return true;
 65         }
 66 
 67         Session session = subject.getSession();
 68         Serializable sessionId = session.getId();
 69 
 70         String usernameTenant = (String)session.getAttribute("loginName");
 71         synchronized (this.cache) {
 72             if(cache == null){
 73                 throw new Exception("cache 為空");
 74             }
 75             Deque<Session> deque = cache.get(usernameTenant);
 76             if (deque == null) {
 77                 deque = new LinkedList<Session>();
 78                 cache.put(usernameTenant, deque);
 79             }
 80 
 81             //如果隊列里沒有此sessionId,且用戶沒有被踢出;放入隊列
 82             boolean whetherPutDeQue = true;
 83             if (deque.isEmpty()) {
 84                 whetherPutDeQue = true;
 85             } else {
 86                 for (Session sessionInqueue : deque) {
 87                     if (sessionId.equals(sessionInqueue.getId())) {
 88                         whetherPutDeQue = false;
 89                         break;
 90                     }
 91                 }
 92             }
 93             if (whetherPutDeQue) {
 94                 deque.push(session);
 95             }
 96             this.LOGGER.debug("logged user:" + usernameTenant + ", deque size = " + deque.size());
 97             this.LOGGER.debug("deque = " + deque);
 98 
 99             //如果隊列里的sessionId數超出最大會話數,開始踢人
100             while (deque.size() > maxSession) {
101                 Session kickoutSession = null;
102                 if (kickoutAfter) { //如果踢出后者
103                     kickoutSession = deque.removeFirst();
104                     this.LOGGER.debug("踢出后登錄的,被踢出的sessionId為: " + kickoutSession.getId());
105                 } else { //否則踢出前者
106                     kickoutSession = deque.removeLast();
107                     this.LOGGER.debug("踢出先登錄的,被踢出的sessionId為: " + kickoutSession.getId());
108                 }
109                 if (kickoutSession != null) {
110  kickoutSession.stop(); 111                 }
112             }
113         }
114         return true;
115     }
116 }

 

3.遇到的錯誤和說明

3.1 共享session的問題

項目中,使用了共享session,出現了踢出失效的問題。(已解決)

解決辦法:原本的實現代碼使用的是標記屬性,現在改為直接stop該session。

之前的代碼:

1  if (kickoutSession != null) {
2         //設置會話的kickout屬性表示踢出了
3         kickoutSession.setAttribute(KICK_OUT, true); 4  }

 

之后的代碼:

1 if (kickoutSession != null) {
2  kickoutSession.stop(); 3 }

 

 

 

 

 

 

 

 

 

 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM