shiro 并发登录人数控制

转自:http://jinnianshilongnian.iteye.com/blog/2039760

在某些项目中可能会遇到如每个账户同时只能有一个人登录或几个人同时登录,如果同时有多人登录:要么不让后者登录;要么踢出前者登录(强制退出)。比如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.   
  6.     <property name="kickoutAfter" value="false"/>  
  7.     <property name="maxSession" value="2"/>  
  8.     <property name="kickoutUrl" value="/login?kickout=1"/>  
  9. </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.   
  8.     Session session = subject.getSession();  
  9.     String username = (String) subject.getPrincipal();  
  10.     Serializable sessionId = session.getId();  
  11.   
  12.     //TODO 同步控制  
  13.     Deque<Serializable> deque = cache.get(username);  
  14.     if(deque == null) {  
  15.         deque = new LinkedList<Serializable>();  
  16.         cache.put(username, deque);  
  17.     }  
  18.   
  19.     //如果队列里没有此sessionId,且用户没有被踢出;放入队列  
  20.     if(!deque.contains(sessionId) && session.getAttribute("kickout") == null) {  
  21.         deque.push(sessionId);  
  22.     }  
  23.   
  24.     //如果队列里的sessionId数超出最大会话数,开始踢人  
  25.     while(deque.size() > maxSession) {  
  26.         Serializable kickoutSessionId = null;  
  27.         if(kickoutAfter) { //如果踢出后者  
  28.             kickoutSessionId = deque.removeFirst();  
  29.         } else { //否则踢出前者  
  30.             kickoutSessionId = deque.removeLast();  
  31.         }  
  32.         try {  
  33.             Session kickoutSession =  
  34.                 sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));  
  35.             if(kickoutSession != null) {  
  36.                 //设置会话的kickout属性表示踢出了  
  37.                 kickoutSession.setAttribute("kickout"true);  
  38.             }  
  39.         } catch (Exception e) {//ignore exception  
  40.         }  
  41.     }  
  42.   
  43.     //如果被踢出了,直接退出,重定向到踢出后的地址  
  44.     if (session.getAttribute("kickout") != null) {  
  45.         //会话被踢出了  
  46.         try {  
  47.             subject.logout();  
  48.         } catch (Exception e) { //ignore  
  49.         }  
  50.         saveRequest(request);  
  51.         WebUtils.issueRedirect(request, response, kickoutUrl);  
  52.         return false;  
  53.     }  
  54.     return true;  
  55. }   

此处使用了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技术。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Shiro 是一个强大且易于使用的Java安全框架,提供了身份认证、授权、加密和会话管理等功能,可以用于构建安全稳定的分布式登录系统。 Shiro 分布式登录的架构可以通过集中式认证、授权服务来实现。在这个架构中,有一个独立的认证授权中心,所有需要登录的应用都将与该中心进行通信。 首先,用户在某个应用中输入用户名和密码进行登录。该应用将用户登录请求发送至认证授权中心。认证授权中心根据接收到的用户名和密码对用户进行认证,验证用户的身份是否合法。 如果认证成功,认证授权中心会生成一份包含用户身份、权限等信息的令牌(Token),并将该令牌返回给应用。应用可以将该令牌保存在客户端,用于后续的访问请求。 当用户访问其他需要登录权限的应用时,该应用会将用户的请求发送至认证授权中心进行令牌验证。认证授权中心会根据令牌中的信息判断用户的身份和权限是否满足要求。 如果验证通过,认证授权中心会给予应用相应的访问权限,用户可以成功访问该应用。如果验证不通过,用户将无法访问该应用。 通过这样的分布式登录架构,可以实现用户只需一次登录,就能访问多个应用的需求。这样的架构具有很好的安全性和可扩展性,适用于大规模分布式系统中。 总之,Shiro 分布式登录是一种通过集中式认证授权中心来实现用户统一登录,访问多个应用的安全方案。它能够提供稳定、安全且高效的分布式登录功能,具备广泛的应用价值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值