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过滤器的主题配置:如下图红色的标注为 新增 或 修改的

 

posted @ 2018-05-25 16:51  ╱、隐风っ九剑  阅读(3033)  评论(0编辑  收藏  举报