原文地址:https://www.cnblogs.com/yinfengjiujian/p/9087511.html
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过滤器的主题配置:如下图红色的标注为 新增 或 修改的