您的位置:首页 > 其它

2017.6.30 用shiro实现并发登录人数控制(实际项目中的实现)

2018-01-04 16:41 645 查看
之前的学习总结: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 }


 

 

 

 

 

 

 

 

 

 
 
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: