目录贴:跟我学Shiro目录贴

在某些项目中可能会遇到如每个账户同时只能有一个人登录或几个人同时登录,如果同时有多人登录:要么不让后者登录;要么踢出前者登录(强制退出)。比如spring security就直接提供了相应的功能;Shiro的话没有提供默认实现,不过可以很容易的在Shiro中加入这个功能。

示例代码基于《第十六章 综合实例》完成,通过Shiro Filter机制扩展KickoutSessionControlFilter完成。

首先来看看如何配置使用(spring-config-shiro.xml

kickoutSessionControlFilter用于控制并发登录人数的

  1. <bean id="kickoutSessionControlFilter"
  2. class="com.github.zhangkaitao.shiro.chapter18.web.shiro.filter.KickoutSessionControlFilter">
  3. <property name="cacheManager" ref="cacheManager"/>
  4. <property name="sessionManager" ref="sessionManager"/>
  5. <property name="kickoutAfter" value="false"/>
  6. <property name="maxSession" value="2"/>
  7. <property name="kickoutUrl" value="/login?kickout=1"/>
  8. </bean>

cacheManager:使用cacheManager获取相应的cache来缓存用户登录的会话;用于保存用户—会话之间的关系的;

sessionManager:用于根据会话ID,获取会话进行踢出操作的;

kickoutAfter:是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;

maxSession:同一个用户最大的会话数,默认1;比如2的意思是同一个用户允许最多同时两个人登录;

kickoutUrl:被踢出后重定向到的地址;

shiroFilter配置

  1. <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
  2. <property name="securityManager" ref="securityManager"/>
  3. <property name="loginUrl" value="/login"/>
  4. <property name="filters">
  5. <util:map>
  6. <entry key="authc" value-ref="formAuthenticationFilter"/>
  7. <entry key="sysUser" value-ref="sysUserFilter"/>
  8. <entry key="kickout" value-ref="kickoutSessionControlFilter"/>
  9. </util:map>
  10. </property>
  11. <property name="filterChainDefinitions">
  12. <value>
  13. /login = authc
  14. /logout = logout
  15. /authenticated = authc
  16. /** = kickout,user,sysUser
  17. </value>
  18. </property>
  19. </bean>

此处配置除了登录等之外的地址都走kickout拦截器进行并发登录控制。

测试

此处因为maxSession=2,所以需要打开3个浏览器(需要不同的浏览器,如IE、Chrome、Firefox),分别访问http://localhost:8080/chapter18/进行登录;然后刷新第一次打开的浏览器,将会被强制退出,如显示下图:

KickoutSessionControlFilter核心代码:

  1. protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
  2. Subject subject = getSubject(request, response);
  3. if(!subject.isAuthenticated() && !subject.isRemembered()) {
  4. //如果没有登录,直接进行之后的流程
  5. return true;
  6. }
  7. Session session = subject.getSession();
  8. String username = (String) subject.getPrincipal();
  9. Serializable sessionId = session.getId();
  10. //TODO 同步控制
  11. Deque<Serializable> deque = cache.get(username);
  12. if(deque == null) {
  13. deque = new LinkedList<Serializable>();
  14. cache.put(username, deque);
  15. }
  16. //如果队列里没有此sessionId,且用户没有被踢出;放入队列
  17. if(!deque.contains(sessionId) && session.getAttribute("kickout") == null) {
  18. deque.push(sessionId);
  19. }
  20. //如果队列里的sessionId数超出最大会话数,开始踢人
  21. while(deque.size() > maxSession) {
  22. Serializable kickoutSessionId = null;
  23. if(kickoutAfter) { //如果踢出后者
  24. kickoutSessionId = deque.removeFirst();
  25. } else { //否则踢出前者
  26. kickoutSessionId = deque.removeLast();
  27. }
  28. try {
  29. Session kickoutSession =
  30. sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));
  31. if(kickoutSession != null) {
  32. //设置会话的kickout属性表示踢出了
  33. kickoutSession.setAttribute("kickout", true);
  34. }
  35. } catch (Exception e) {//ignore exception
  36. }
  37. }
  38. //如果被踢出了,直接退出,重定向到踢出后的地址
  39. if (session.getAttribute("kickout") != null) {
  40. //会话被踢出了
  41. try {
  42. subject.logout();
  43. } catch (Exception e) { //ignore
  44. }
  45. saveRequest(request);
  46. WebUtils.issueRedirect(request, response, kickoutUrl);
  47. return false;
  48. }
  49. return true;
  50. }

此处使用了Cache缓存用户名—会话id之间的关系;如果量比较大可以考虑如持久化到数据库/其他带持久化的Cache中;另外此处没有并发控制的同步实现,可以考虑根据用户名获取锁来控制,减少锁的粒度。

另外可参考JavaEE项目开发脚手架,其提供了后台踢出用户的功能:

https://github.com/zhangkaitao/es/blob/master/web/src/main/java/com/sishuok/es/sys/user/web/controller/UserOnlineController.java

示例源代码:https://github.com/zhangkaitao/shiro-example;可加群 231889722 探讨Spring/Shiro技术。

第十八章 并发登录人数控制——《跟我学Shiro》的更多相关文章

  1. 2017.4.12 开涛shiro教程-第十八章-并发登录人数控制

    原博客地址:http://jinnianshilongnian.iteye.com/blog/2018398 根据下载的pdf学习. 开涛shiro教程-第十八章-并发登录人数控制 shiro中没有提 ...

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

    之前的学习总结:http://www.cnblogs.com/lyh421/p/6698871.html 1.kickout功能描述 如果将配置文件中的kickout设置为true,则在另处再次登录时 ...

  3. SpringBoot 并发登录人数控制

    通常系统都会限制同一个账号的登录人数,多人登录要么限制后者登录,要么踢出前者,Spring Security 提供了这样的功能,本文讲解一下在没有使用Security的时候如何手动实现这个功能 dem ...

  4. 并发登录人数控制--Shiro系列(二)

    为了安全起见,同一个账号理应同时只能在一台设备上登录,后面登录的踢出前面登录的.用Shiro可以轻松实现此功能. shiro中sessionManager是专门作会话管理的,而sessinManage ...

  5. 第十六章 综合实例——《跟我学Shiro》

    简单的实体关系图 简单数据字典 用户(sys_user) 名称 类型 长度 描述 id bigint 编号 主键 username varchar 100 用户名 password varchar 1 ...

  6. 第二十二章 集成验证码——《跟我学Shiro》

    目录贴:跟我学Shiro目录贴 在做用户登录功能时,很多时候都需要验证码支持,验证码的目的是为了防止机器人模拟真实用户登录而恶意访问,如暴力破解用户密码/恶意评论等.目前也有一些验证码比较简单,通过一 ...

  7. 十六章 综合实例——《跟我学Shiro》

    目录贴:跟我学Shiro目录贴 简单的实体关系图 简单数据字典 用户(sys_user) 名称 类型 长度 描述 id bigint 编号 主键 username varchar 100 用户名 pa ...

  8. 第八章 拦截器机制——《跟我学Shiro》

    转发地址:https://www.iteye.com/blog/jinnianshilongnian-2025656 博客分类: 跟我学Shiro 跟我学Shiro  目录贴:跟我学Shiro目录贴 ...

  9. TCP/IP详解 卷1 第十八章 TCP的建立与终止

    第十八章 TCP的建立与终止 tcpdump Tcpdump可以将网络中传送的数据报完截获下来进行分析.它支持针对网络层.协议.主机.网络或端口的过滤,并提供and.or.not等逻辑语句来帮助你去掉 ...

随机推荐

  1. python类内置方法之__call__

    在python中自定义类时,如果该类实现了一个特殊方法__call__(),那么该类的实例则变成一个可调用的实例对象 如下 In [1]: class A():# 自定义一个A ...: def __ ...

  2. [Dart] Understand Classes and Inheritance in Dart

    We will look at how we can create classes and explore some various features. Dart adopts a single-in ...

  3. Soda Theme sublime 自定义编辑器主题

    1.Soda ThemeSublime Text 3中较为常用的一款自定义编辑器主题,用过的人都说好.Soda Theme包含代码着色.标签.图标,拥有light和dark两种颜色主题便于用户在不同时 ...

  4. 什么是webpack模块化构建工具

    百度百科模块化:是指解决一个复杂问题时自顶向下逐层把系统划分成若干模块的过程,有多种属性,分别反映其内部特性. 计算机模块化:一般指的是可以被抽象封装的最小/最优代码集合,模块化解决的是功能耦合问题. ...

  5. Vue.js-组件化前端开发新思路

    Vue.js-组件化前端开发新思路 12017.04.14 18:31:25字数 6228阅读 5632 本文章是我最近在公司的一场内部分享的内容.我有个习惯就是每次分享都会先将要分享的内容写成文章. ...

  6. 接口实现后台GZIP压缩,pako.js 前端解压

    import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException; ...

  7. MongoDB 分片管理(四)数据均衡 -- 特大快

    1.1 特大快形成 如果用date字段作为片键,集合中date是一个日期字符串,如:year/month/day,也就是说,mongoDB一天创建一个块.因块内所有文档的片键一样,因此这些块是不可拆分 ...

  8. 西门子二次开发--HMI failed to start

    一.Sinumerik二次开发错误:HMI failed to start. HMI--SL Framework reported error: GUI Component could not be ...

  9. 爬虫(十三):scrapy中pipeline的用法

    当Item 在Spider中被收集之后,就会被传递到Item Pipeline中进行处理 每个item pipeline组件是实现了简单的方法的python类,负责接收到item并通过它执行一些行为, ...

  10. Alphat【翻译】

    翻译自:CFD-online 帖子地址:http://www.cfd-online.com/Forums/openfoam-solving/144625-alphat.html Wien3: 早上好 ...