Shiro和Spring 集合實現同一個賬號只能一個人在線使用,其它人在使用進行剔除(八)


1、實現原理其實就是自定義過濾器,然后登錄時,A登錄系統后,B也登錄了,這個時候獲取此賬號之前的session給刪除,然后將新的session放入到緩存里面去,一個賬戶對應一個有序的集合

編寫自定義過濾器:KickoutSessionControlFilter.java

  1 import java.io.Serializable;
  2 import java.util.Deque;
  3 import java.util.LinkedList;
  4 
  5 import javax.servlet.ServletRequest;
  6 import javax.servlet.ServletResponse;
  7 import javax.servlet.http.HttpServletRequest;
  8 import javax.servlet.http.HttpServletResponse;
  9 
 10 import org.apache.shiro.cache.Cache;
 11 import org.apache.shiro.cache.CacheManager;
 12 import org.apache.shiro.session.Session;
 13 import org.apache.shiro.session.mgt.DefaultSessionKey;
 14 import org.apache.shiro.session.mgt.SessionManager;
 15 import org.apache.shiro.subject.Subject;
 16 import org.apache.shiro.web.filter.AccessControlFilter;
 17 import org.apache.shiro.web.util.WebUtils;
 18 
 19 import com.itzixi.pojo.ActiveUser;
 20 
 21 /**
 22  * 
 23  * @Title: KickoutSessionControlFilter.java
 24  * @Description: 同一用戶后登陸踢出前面的用戶
 25  * @date 2016年12月12日 下午7:25:40
 26  * @version V1.0
 27  */
 28 public class KickoutSessionControlFilter extends AccessControlFilter {
 29 
 30     private String kickoutUrl; //踢出后到的地址
 31     private boolean kickoutAfter = false; //踢出之前登錄的/之后登錄的用戶 默認踢出之前登錄的用戶
 32     private int maxSession = 1; //同一個帳號最大會話數 默認1
 33 
 34     private SessionManager sessionManager;
 35     
 36     // TODO 分布式集群環境下,需要改為redis
 37     private Cache<String, Deque<Serializable>> cache;
 38 
 39     public void setKickoutUrl(String kickoutUrl) {
 40         this.kickoutUrl = kickoutUrl;
 41     }
 42 
 43     public void setKickoutAfter(boolean kickoutAfter) {
 44         this.kickoutAfter = kickoutAfter;
 45     }
 46 
 47     public void setMaxSession(int maxSession) {
 48         this.maxSession = maxSession;
 49     }
 50 
 51     public void setSessionManager(SessionManager sessionManager) {
 52         this.sessionManager = sessionManager;
 53     }
 54 
 55     public void setCacheManager(CacheManager cacheManager) {
 56         this.cache = cacheManager.getCache("shiro-kickout-session");
 57     }
 58 
 59     @Override
 60     protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
 61         return false;
 62     }
 63 
 64     @Override
 65     protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
 66         Subject subject = getSubject(request, response);
 67         if(!subject.isAuthenticated() && !subject.isRemembered()) {
 68             //如果沒有登錄,直接進行之后的流程
 69             return true;
 70         }
 71 
 72         Session session = subject.getSession();
 73         ActiveUser user = (ActiveUser)subject.getPrincipal();
 74         String username = user.getUsername();
 75         Serializable sessionId = session.getId();
 76 
 77         // 同步控制, 同步在本機的緩存中是有效的,但是一旦放入集群中,就會失效
 78         Deque<Serializable> deque = cache.get(username);
 79         if(deque == null) {
 80             deque = new LinkedList<Serializable>();
 81             cache.put(username, deque);
 82         }
 83 
 84         //如果隊列里沒有此sessionId,且用戶沒有被踢出;放入隊列
 85         if(!deque.contains(sessionId) && session.getAttribute("kickout") == null) {
 86             deque.push(sessionId);
 87         }
 88 
 89         //如果隊列里的sessionId數超出最大會話數,開始踢人
 90         while(deque.size() > maxSession) {
 91             Serializable kickoutSessionId = null;
 92             if(kickoutAfter) { //如果踢出后者
 93                 kickoutSessionId = deque.removeFirst();
 94             } else { //否則踢出前者
 95                 kickoutSessionId = deque.removeLast();
 96             }
 97             try {
 98                 Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));
 99                 if(kickoutSession != null) {
100                     //設置會話的kickout屬性表示踢出了
101                     kickoutSession.setAttribute("kickout", true);
102                 }
103             } catch (Exception e) {//ignore exception
104             }
105         }
106 
107         //如果被踢出了,直接退出,重定向到踢出后的地址
108         if (session.getAttribute("kickout") != null) {
109             //會話被踢出了
110             try {
111                 subject.logout();
112             } catch (Exception e) { //ignore
113             }
114             saveRequest(request);
115             
116             HttpServletRequest httpRequest = WebUtils.toHttp(request);
117             if (ShiroFilterUtils.isAjax(httpRequest)) {
118                 HttpServletResponse httpServletResponse = WebUtils.toHttp(response);  
119                 httpServletResponse.sendError(ShiroFilterUtils.HTTP_STATUS_SESSION_EXPIRE);
120                 return false;
121             } else {
122                 WebUtils.issueRedirect(request, response, kickoutUrl);
123                 return false;
124             }
125         }
126 
127         return true;
128     }
129 }

2、在applicationContext-shiro.xml配置文件中增加如下配置: 

   注意:必須使用本機的ehcache緩存來存儲,不能使用集群的redis緩存

 1 <!--自定義filter實現同一個賬戶只能同時一個人在線,后者登錄的踢出前面登錄的用戶-->
 2     <bean id="kickoutSessionControlFilter" class="com.itzixi.web.shiro.filter.KickoutSessionControlFilter">
 3         <property name="cacheManager" ref="shiroEhcacheManager"/>
 4         <property name="sessionManager" ref="sessionManager"/>
 5         <!-- 是否踢出后來登錄的,默認是false;即后者登錄的用戶踢出前者登錄的用戶 -->
 6         <property name="kickoutAfter" value="false"/>
 7         <!-- 同一個用戶最大的會話數,默認1;比如2的意思是同一個用戶允許最多同時兩個人登錄 -->
 8         <property name="maxSession" value="1"/>
 9         <property name="kickoutUrl" value="/login.action"/>
10     </bean>

3、修改shiro過濾器的主題配置:如下圖紅色的標注為 新增 或 修改的

 


免責聲明!

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



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