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


    之前的学习总结:http://www.cnblogs.com/lyh421/p/6698871.html

    1.kickout功能描述

    如果将配置文件中的kickout设置为true,则在另处再次登录时,会将第一次登录的用户踢出。
     

    2.kickout的实现

    2.1 新建KickoutSessionControlFilter extends AccessControlFilter

    详细的方法实现,后面再来完成。类存放于公共module:base_project中。

     1 public class KickoutSessionControlFilter extends AccessControlFilter {
     2     @Override
     3     protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
     4         return false;
     5     }
     6 
     7     @Override
     8     protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
     9         return false;
    10     }
    11 }

    2.2 配置spring-config-shiro.xml

    这两个文件配置在要使用kickout功能的module中。

    (1)kickoutSessionControllerFilter

    kickoutAfter:是否提出后来登录的,默认为false,即后来登录的踢出前者。

    maxSession:同一个用户的最大会话数,默认1,表示同一个用户最多同时一个人登录。

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

    1 <!--并发登录控制-->
    2     <bean id="kickoutSessionControlFilter" class="***.common.filter.KickoutSessionControlFilter">
    3         <property name="cacheManager" ref="springCacheManager"/>
    4         <property name="kickoutAfter" value="false"/>
    5         <property name="maxSession" value="1"/>
    6         <property name="kickoutUrl" value="/login.do"/>
    7     </bean>

    (2)shiroFilter

    此处配置什么时候走kickout 拦截器,进行并发登录控制。这里拦截所有.jsp和.do的路径。

     1 <bean id="AuthRequestFilter" class="com.baosight.aas.auth.filter.AuthRequestFilter"/>
     2     <!-- Shiro主过滤器本身功能十分强大,其强大之处就在于它支持任何基于URL路径表达式的、自定义的过滤器的执行 -->
     3     <!-- Web应用中,Shiro可控制的Web请求必须经过Shiro主过滤器的拦截,Shiro对基于Spring的Web应用提供了完美的支持 -->
     4     <bean id="shiroFilter" class="com.baosight.aas.auth.filter.factory.ClientShiroFilterFactoryBean">
               //略
    13         <property name="filters">
    14             <util:map>
    15                 <entry key="authc" value-ref="formAuthenticationFilter"/>
                       //略
    19                 <entry key="kickout" value-ref="kickoutSessionControlFilter"/>
    20             </util:map>
    21         </property>
    27         <property name="filterChainDefinitions">
    28             <value>
                       //略48                 /**/*.jsp = forceLogout,authc,kickout
    49                 /**/*.do = forceLogout,authc,kickout
    50                 /** = forceLogout,authc
    51             </value>
    52         </property>
    53     </bean>

    2.3 ehcache.xml

    注意,其他module在配置shiro的时候,都是使用的公共module:base_project中的ehcache.xml文件。在此文件中加上一段:

    这里的名称shiro-kickout-session在后面的kickoutController里要用到。

    1 <cache name="shiro-kickout-session"
    2            eternal="false"
    3            timeToIdleSeconds="3600"
    4            timeToLiveSeconds="0"
    5            overflowToDisk="false"
    6            statistics="true">
    7     </cache>

    2.4 实现KickoutSessionControlFilter

      1 package com.baosight.common.filter;
      2 
      3 import org.apache.shiro.cache.Cache;
      4 import org.apache.shiro.cache.CacheManager;
      5 import org.apache.shiro.session.Session;
      6 import org.apache.shiro.subject.Subject;
      7 import org.apache.shiro.web.filter.AccessControlFilter;
      8 import org.slf4j.Logger;
      9 import org.slf4j.LoggerFactory;
     10 import org.springframework.beans.factory.annotation.Value;
     11 
     12 import javax.servlet.ServletRequest;
     13 import javax.servlet.ServletResponse;
     14 import java.io.Serializable;
     15 import java.util.Deque;
     16 import java.util.LinkedList;
     17 
     18 /**
     19  * Created by liyuhui on 2017/4/12.
     20  */
     21 public class KickoutSessionControlFilter extends AccessControlFilter{
     22     private static final Logger LOGGER = LoggerFactory.getLogger(KickoutSessionControlFilter.class);
     23 
     24     @Value("${aas.kickout:false}")
     25     private String kickout;
     26 
     27     private String kickoutUrl;
     28     private boolean kickoutAfter = false;
     29     private int maxSession = 1;
     31     private Cache<String, Deque<Session>> cache;
     32 
     33     public void setKickoutUrl(String kickoutUrl) {
     34         this.kickoutUrl = kickoutUrl;
     35     }
     36 
     37     public void setKickoutAfter(boolean kickoutAfter) {
     38         this.kickoutAfter = kickoutAfter;
     39     }
     40 
     41     public void setMaxSession(int maxSession) {
     42         this.maxSession = maxSession;
     43     }
     44 
     45     public void setCacheManager(CacheManager cacheManager) {
     46         this.cache = cacheManager.getCache("shiro-kickout-session");
     47     }
     48 
     49     @Override
     50     protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
     51         return false;
     52     }
     53 
     54     @Override
     55     protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
     56         if(!"true".equals(kickout)){
     57             //如果不需要单用户登录的限制
     58             return true;
     59         }
     60 
     61         Subject subject = getSubject(request, response);
     62         if(!subject.isAuthenticated() && !subject.isRemembered()){
     63             //如果没登录,直接进行之后的流程
     64             return true;
     65         }
     66 
     67         Session session = subject.getSession();
     68         Serializable sessionId = session.getId();
     69 
     70         String usernameTenant = (String)session.getAttribute("loginName");
     71         synchronized (this.cache) {
     72             if(cache == null){
     73                 throw new Exception("cache 为空");
     74             }
     75             Deque<Session> deque = cache.get(usernameTenant);
     76             if (deque == null) {
     77                 deque = new LinkedList<Session>();
     78                 cache.put(usernameTenant, deque);
     79             }
     80 
     81             //如果队列里没有此sessionId,且用户没有被踢出;放入队列
     82             boolean whetherPutDeQue = true;
     83             if (deque.isEmpty()) {
     84                 whetherPutDeQue = true;
     85             } else {
     86                 for (Session sessionInqueue : deque) {
     87                     if (sessionId.equals(sessionInqueue.getId())) {
     88                         whetherPutDeQue = false;
     89                         break;
     90                     }
     91                 }
     92             }
     93             if (whetherPutDeQue) {
     94                 deque.push(session);
     95             }
     96             this.LOGGER.debug("logged user:" + usernameTenant + ", deque size = " + deque.size());
     97             this.LOGGER.debug("deque = " + deque);
     98 
     99             //如果队列里的sessionId数超出最大会话数,开始踢人
    100             while (deque.size() > maxSession) {
    101                 Session kickoutSession = null;
    102                 if (kickoutAfter) { //如果踢出后者
    103                     kickoutSession = deque.removeFirst();
    104                     this.LOGGER.debug("踢出后登录的,被踢出的sessionId为: " + kickoutSession.getId());
    105                 } else { //否则踢出前者
    106                     kickoutSession = deque.removeLast();
    107                     this.LOGGER.debug("踢出先登录的,被踢出的sessionId为: " + kickoutSession.getId());
    108                 }
    109                 if (kickoutSession != null) {
    110                     kickoutSession.stop();
    111                 }
    112             }
    113         }
    114         return true;
    115     }
    116 }

    3.遇到的错误和说明

    3.1 共享session的问题

    项目中,使用了共享session,出现了踢出失效的问题。(已解决)

    解决办法:原本的实现代码使用的是标记属性,现在改为直接stop该session。

    之前的代码:

    1  if (kickoutSession != null) {
    2         //设置会话的kickout属性表示踢出了
    3         kickoutSession.setAttribute(KICK_OUT, true);
    4  }

    之后的代码:

    1 if (kickoutSession != null) {
    2       kickoutSession.stop();
    3 }
     
     
     
  • 相关阅读:
    《算法导论》读书笔记(五)
    《算法导论》读书笔记(四)
    《算法导论》读书笔记(三)
    《算法导论》读书笔记(二)
    《算法导论》读书笔记(一)
    Posix消息队列
    管道和FIFO
    linux内核数据结构之kfifo
    linux内核数据结构之链表
    Mybatis XML 映射配置文件 -- 熟悉配置
  • 原文地址:https://www.cnblogs.com/lyh421/p/7099541.html
Copyright © 2020-2023  润新知