您好,登錄后才能下訂單哦!
在某些項目中可能會遇到如每個賬戶同時只能有一個人登錄或幾個人同時登錄,如果同時有多人登錄:要么不讓后者登錄;要么踢出前者登錄(強制退出)。比如spring security就直接提供了相應的功能;Shiro的話沒有提供默認實現(xiàn),不過可以很容易的在Shiro中加入這個功能。
通過Shiro Filter機制擴展KickoutSessionControlFilter完成。
首先來看看如何配置使用(spring-config-shiro.xml)
kickoutSessionControlFilter用于控制并發(fā)登錄人數(shù)的
Java代碼
<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獲取相應的cache來緩存用戶登錄的會話;用于保存用戶—會話之間的關系的;
sessionManager:用于根據(jù)會話ID,獲取會話進行踢出操作的;
kickoutAfter:是否踢出后來登錄的,默認是false;即后者登錄的用戶踢出前者登錄的用戶;
maxSession:同一個用戶最大的會話數(shù),默認1;比如2的意思是同一個用戶允許最多同時兩個人登錄;
kickoutUrl:被踢出后重定向到的地址;
shiroFilter配置
Java代碼
<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攔截器進行并發(fā)登錄控制。
測試
此處因為maxSession=2,所以需要打開3個瀏覽器(需要不同的瀏覽器,如IE、Chrome、Firefox),分別訪問http://localhost:8080/chapter18/進行登錄;然后刷新第一次打開的瀏覽器,將會被強制退出,如顯示下圖:
KickoutSessionControlFilter核心代碼:
Java代碼
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(); //TODO 同步控制 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數(shù)超出最大會話數(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) { //設置會話的kickout屬性表示踢出了 kickoutSession.setAttribute("kickout", true); } } catch (Exception e) {//ignore exception } } //如果被踢出了,直接退出,重定向到踢出后的地址 if (session.getAttribute("kickout") != null) { //會話被踢出了 try { subject.logout(); } catch (Exception e) { //ignore } saveRequest(request); WebUtils.issueRedirect(request, response, kickoutUrl); return false; } return true; }
此處使用了Cache緩存用戶名—會話id之間的關系;如果量比較大可以考慮如持久化到數(shù)據(jù)庫/其他帶持久化的Cache中;另外此處沒有并發(fā)控制的同步實現(xiàn),可以考慮根據(jù)用戶名獲取鎖來控制,減少鎖的粒度。
總結
以上所述是小編給大家介紹的shiro并發(fā)人數(shù)登錄控制的實現(xiàn)代碼,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網站的支持!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經查實,將立刻刪除涉嫌侵權內容。