Shiro学习(18)并发人数限制
在某些项目中可能会遇到如每个账户同时只能有一个人登录或几个人同时登录,如果同时有多人登录:要么不让后者登录;要么踢出前者登录(强制退出)。比如spring security就直接提供了相应的功能;Shiro的话没有提供默认实现,不过可以很容易的在Shiro中加入这个功能。
示例代码基于《第十六章 综合实例》完成,通过Shiro Filter机制扩展KickoutSessionControlFilter完成。
首先来看看如何配置使用(spring-config-shiro.xml)
kickoutSessionControlFilter用于控制并发登录人数的

- <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:用于根据会话ID,获取会话进行踢出操作的;
kickoutAfter:是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;
maxSession:同一个用户最大的会话数,默认1;比如2的意思是同一个用户允许最多同时两个人登录;
kickoutUrl:被踢出后重定向到的地址;
shiroFilter配置

- <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拦截器进行并发登录控制。
此处因为maxSession=2,所以需要打开3个浏览器(需要不同的浏览器,如IE、Chrome、Firefox),分别访问http://localhost:8080/chapter18/进行登录;然后刷新第一次打开的浏览器,将会被强制退出,如显示下图:
KickoutSessionControlFilter核心代码:

- 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数超出最大会话数,开始踢人
- 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之间的关系;如果量比较大可以考虑如持久化到数据库/其他带持久化的Cache中;另外此处没有并发控制的同步实现,可以考虑根据用户名获取锁来控制,减少锁的粒度。
另外可参考JavaEE项目开发脚手架,其提供了后台踢出用户的功能:
Shiro学习(18)并发人数限制的更多相关文章
- Apache shiro学习总结
Apache shiro集群实现 (一) shiro入门介绍 Apache shiro集群实现 (二) shiro 的INI配置 Apache shiro集群实现 (三)shiro身份认证(Shiro ...
- Shiro学习
Shiro学习资源 Shiro官网,http://shiro.apache.org/index.html 学习网站链接,http://blog.java1234.com/blog/articles/4 ...
- Shiro学习笔记(5)——web集成
Web集成 shiro配置文件shiroini 界面 webxml最关键 Servlet 測试 基于 Basic 的拦截器身份验证 Web集成 大多数情况.web项目都会集成spring.shiro在 ...
- 如何才能够系统地学习Java并发技术?
微信公众号[Java技术江湖]一位阿里Java工程师的技术小站 Java并发编程一直是Java程序员必须懂但又是很难懂的技术内容. 这里不仅仅是指使用简单的多线程编程,或者使用juc的某个类.当然这些 ...
- Apache Shiro学习-2-Apache Shiro Web Support
Apache Shiro Web Support 1. 配置 将 Shiro 整合到 Web 应用中的最简单方式是在 web.xml 的 Servlet ContextListener 和 Fil ...
- shiro学习笔记_0600_自定义realm实现授权
博客shiro学习笔记_0400_自定义Realm实现身份认证 介绍了认证,这里介绍授权. 1,仅仅通过配置文件来指定权限不够灵活且不方便.在实际的应用中大多数情况下都是将用户信息,角色信息,权限信息 ...
- Shiro学习笔记总结,附加" 身份认证 "源码案例(一)
Shiro学习笔记总结 内容介绍: 一.Shiro介绍 二.subject认证主体 三.身份认证流程 四.Realm & JDBC reaml介绍 五.Shiro.ini配置介绍 六.源码案例 ...
- SpringBoot+Shiro学习(七):Filter过滤器管理
SpringBoot+Shiro学习(七):Filter过滤器管理 Hiwayz 关注 0.5 2018.09.06 19:09* 字数 1070 阅读 5922评论 1喜欢 20 先从我们写的一个 ...
- 从源码学习Java并发的锁是怎么维护内部线程队列的
从源码学习Java并发的锁是怎么维护内部线程队列的 在上一篇文章中,凯哥对同步组件基础框架- AbstractQueuedSynchronizer(AQS)做了大概的介绍.我们知道AQS能够通过内置的 ...
- 如何深入学习Java并发编程?
在讲解深入学习Java并发编程的方法之前,先分析如下若干错误的观点和学习方法. 错误观点1:学习Java编程主要是学习多线程. 这话其实是说明了表面现象,多线程其实还真是并发编程的实现方式,但在实际高 ...
随机推荐
- [bzoj2456]mode 题解
改题改自闭的时候当然要靠水题来调节心情(逃 2456: mode Time Limit: 1 Sec Memory Limit: 1 MBSubmit: 8461 Solved: 3171[Sub ...
- [NOIP模拟26]题解
今天的考试题改自闭了……所以滚来写陈年题解. A.*****贪婪***** RT,出题人告诉我们这题要贪心. 最优的策略一定是拖到必须断的时候再断开(虽然并不知道为什么). 如果一段序列满足题目中的性 ...
- app = Flask(__name__) 是个什么东西
"""第一部分,初始化:所有的Flask都必须创建程序实例, web服务器使用wsgi协议,把客户端所有的请求都转发给这个程序实例 程序实例是Flask的对象,一般情况下 ...
- QC10迁移到ALM11
转自原作者 http://blog.csdn.net/yhqun/article/details/6981250 服务器A:QC9或QC10服务器B:QC9或QC10 DB Server服务器C:AL ...
- 栈Stack --- 数组实现
栈最大的一个特点就是先进后出(FILO—First-In/Last-Out). /** * 栈:后进先出 * Created by fred on 2018/7/31. */ public class ...
- C++之前置自增与后置自增
关于前置自增与后置自增的区别我是参考这里:http://bbs.bccn.net/thread-454977-1-1.html 简单复述下,比如++x; 与 x++; 在C中,++x这个表达式的值为原 ...
- jQuery Mobile 自定义导航条图标
1.jQuery Mobile 自定义导航条图标
- jquery给表格绑值
jquery给表格绑值 直接上代码了 <!DOCTYPE html> <html> <head> <meta charset="UTF-8" ...
- HSQL基本使用(linux),安装+Demo
文章目录 下载 安装 运行 使用数据库 demo 注意 下载 http://sourceforge.net/projects/hsqldb/files/ 安装 将下载的包,解压到任意目录即可 运行 通 ...
- Prometheus 详解
Prometheus 章节 1.Prometheus 简介 2.Prometheus 安装与配置 3.Exporter 4.Pushgateway 5.本地存储和远程存储 6.高可用方案 7.报警插件 ...