Shiro学习(18)并发人数限制

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

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

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

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

Java代码  

  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配置

Java代码  

  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核心代码:

Java代码  

  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

时间: 2024-11-14 20:31:53

Shiro学习(18)并发人数限制的相关文章

shiro学习笔记_0600_自定义realm实现授权

博客shiro学习笔记_0400_自定义Realm实现身份认证 介绍了认证,这里介绍授权. 1,仅仅通过配置文件来指定权限不够灵活且不方便.在实际的应用中大多数情况下都是将用户信息,角色信息,权限信息 保存到了数据库中.所以需要从数据库中去获取相关的数据信息.可以使用 shiro 提供的JdbcRealm来实现,,也可以自定义realm来实现.使用jdbcRealm往往也不够灵活.所以在实际应用中大多数情况都是自定义Realm来实现. 2,自定义Realm 需要继承 AuthorizingRea

ndk学习18: JNI之C&C++调用Java

一.Java反射机制 先了解Java反射机制原理 例子网上很多,反射很灵活 二. 在JNI层调Java 用途: Java层逆向比较容易,增加逆向门槛,把调用都写到JNI层,  让Java层无调用关系 注意: C和C++有所不同,这里使用C++作为例子 1. 首先在代码中提供一个ShowLog函数 private void ShowMsg() { Log.d("_BING_", "ShowMsg"); } 2. 在上次的Add例子加入代码 JNIEXPORT jint

Apache Shiro学习笔记(六)FilterChain

鲁春利的工作笔记,好记性不如烂笔头 Apache Shiro学习笔记(七)IniWebEnvironment

Shiro学习笔记(5)——web集成

Web集成 shiro配置文件shiroini 界面 webxml最关键 Servlet 測试 基于 Basic 的拦截器身份验证 Web集成 大多数情况.web项目都会集成spring.shiro在普通web项目和spring项目中的配置是不一样的.关于spring-shiro集成,能够參考Shiro学习笔记(3)--授权(Authorization) 中的JSP标签授权部分演示样例代码 本次介绍普通的web项目,不使用不论什么框架. shiro配置文件(shiro.ini) 创建web项目.

Shiro学习笔记(2)——身份验证之Realm

环境准备 什么是Realm 为什么要用Realm 自定义Realm 多个Realm 配置Authenticator和AuthenticationStrategy 自定义AuthenticationStrategy验证策略 多个Realm验证顺序 环境准备 创建java工程 需要的jar包 大家也可以使用maven,参考官网 什么是Realm 在我所看的学习资料中,关于Realm的定义,写了整整一长串,但是对于初学者来说,看定义实在是太头疼了. 对于什么是Realm,我使用过之后,个人总结一下:s

Shiro学习笔记(3)——授权(Authorization)

什么是授权 授权三要素 Shiro的三种授权方式 1 编码方式授权 2 基于注解的授权 3 JSP标签授权 1.什么是授权 授权,就是访问控制,控制某个用户在应用程序中是否有权限做某件事 2.授权三要素 权限 请看Shiro学习笔记(1)--shiro入门中权限部分内容 角色 通常代表一组行为或职责.这些行为演化为你在一个软件应用中能或者不能做的事情.角色通常是分配给用户帐户的,因此,通过分配,用户能够"做"的事情可以归属于各种角色 隐式角色:一个角色代表着一系列的操作,当需要对某一操

Shiro学习笔记(4)——ini 配置

ini 配置文件 在前面三个笔记中也有使用到ini配置文件,但是没有进行详细的解析,本次来介绍一下如何配置. ini配置文件其实和properties配置文件一样的使用方法,都是键值对的形式(key=value),#号代表注释 ini配置中主要配置有四大类:main,users,roles,urls [main] #提供了对根对象 securityManager 及其依赖的配置 securityManager=org.apache.shiro.mgt.DefaultSecurityManager

如何才能够系统地学习Java并发技术?

Java并发编程一直是Java程序员必须懂但又是很难懂的技术内容. 这里不仅仅是指使用简单的多线程编程,或者使用juc的某个类.当然这些都是并发编程的基本知识,除了使用这些工具以外,Java并发编程中涉及到的技术原理十分丰富.为了更好地把并发知识形成一个体系,也鉴于本人目前也没有能力写出这类文章,于是参考几位并发编程方面专家的博客和书籍,做一个简单的整理. 首先说一下我学习Java并发编程的一些方法吧.大概分为这几步: 1.先学会最基础的Java多线程编程,Thread类的使用,线程通信的一些方

爬虫学习18.增量式爬虫

爬虫学习18.增量式爬虫 增量式爬虫 引言: 当我们在浏览相关网页的时候会发现,某些网站定时会在原有网页数据的基础上更新一批数据,例如某电影网站会实时更新一批最近热门的电影.小说网站会根据作者创作的进度实时更新最新的章节数据等等.那么,类似的情景,当我们在爬虫的过程中遇到时,我们是不是需要定时更新程序以便能爬取到网站中最近更新的数据呢? 一.增量式爬虫 概念:通过爬虫程序监测某网站数据更新的情况,以便可以爬取到该网站更新出的新数据. 如何进行增量式的爬取工作: 在发送请求之前判断这个URL是不是