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過濾器的主題配置:如下圖紅色的標注為 新增 或 修改的