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