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

来源:互联网 发布:服务器开放端口 编辑:程序博客网 时间:2024/05/19 19:34

为了安全起见,同一个账号理应同时只能在一台设备上登录,后面登录的踢出前面登录的。用Shiro可以轻松实现此功能。

shiro中sessionManager是专门作会话管理的,而sessinManager将会话保存在sessionDAO中,如果不给sessionManager注入

sessionDAO,会话将是瞬时状态,没有被保存起来,从sessionManager里取session,是取不到的。

此例中sessionDAO注入了Ehcache缓存,会话被保存在Ehcache中,不知Ehcache为何物的,请自行查阅资料。

完整demo下载:http://download.csdn.net/detail/qq_33556185/9555720


shiro.xml

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beans       http://www.springframework.org/schema/beans/spring-beans-4.2.xsd       http://www.springframework.org/schema/tx       http://www.springframework.org/schema/tx/spring-tx-4.2.xsd      http://www.springframework.org/schema/context      http://www.springframework.org/schema/context/spring-context-4.2.xsd      http://www.springframework.org/schema/mvc      http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd">     <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">          <property name="globalSessionTimeout" value="1800000"/>          <property name="deleteInvalidSessions" value="true"/>          <property name="sessionDAO" ref="sessionDAO"/>          <property name="sessionIdCookieEnabled" value="true"/>          <property name="sessionIdCookie" ref="sessionIdCookie"/>          <property name="sessionValidationSchedulerEnabled" value="true"/>        <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>         <property name="cacheManager" ref="shiroEhcacheManager"/></bean>     <!-- 会话DAO,sessionManager里面的session需要保存在会话Dao里,没有会话Dao,session是瞬时的,没法从     sessionManager里面拿到session -->      <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">          <property name="sessionIdGenerator" ref="sessionIdGenerator"/>          <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>    </bean>   <!-- 缓存管理器 -->  <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">    <property name="cacheManagerConfigFile" value="classpath:ehcache.xml" />    </bean><bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">          <constructor-arg value="sid"/>        <property name="httpOnly" value="true"/>        <property name="maxAge" value="-1"/> </bean>  <!-- 会话ID生成器 --> <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"></bean><bean id="kickoutSessionControlFilter"  class="com.core.shiro.KickoutSessionControlFilter">      <property name="sessionManager" ref="sessionManager"/>      <property name="cacheManager" ref="shiroEhcacheManager"/>    <property name="kickoutAfter" value="false"/>      <property name="maxSession" value="1"/>      <property name="kickoutUrl" value="/toLogin?kickout=1"/>  </bean>       <bean id="logout" class="org.apache.shiro.web.filter.authc.LogoutFilter">        <property name="redirectUrl" value="/toLogin" />       </bean>           <!-- 会话验证调度器 -->    <bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler ">        <property name="interval" value="1800000"/>        <property name="sessionManager" ref="sessionManager"/>    </bean> <!-- Shiro Filter 拦截器相关配置 -->      <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">          <!-- securityManager -->          <property name="securityManager" ref="securityManager" />          <!-- 登录路径 -->          <property name="loginUrl" value="/toLogin" />          <!-- 用户访问无权限的链接时跳转此页面  -->          <property name="unauthorizedUrl" value="/unauthorizedUrl.jsp" />                 <!-- 过滤链定义 -->           <property name="filters">           <map>               <entry key="kickout" value-ref="kickoutSessionControlFilter"/>           </map>       </property>         <property name="filterChainDefinitions">              <value>              /loginin=kickout,anon             /logout = logout            /toLogin=anon            /css/**=anon                 /html/**=anon                 /images/**=anon                /js/**=anon                 /upload/**=anon                 <!-- /userList=roles[admin] -->                /userList=kickout,authc,perms[/userList]                /toRegister=kickout,authc,perms[/toRegister]                /toDeleteUser=kickout,authc,perms[/toDeleteUser]                /** = kickout,authc             </value>          </property>      </bean>        <!-- securityManager -->      <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">          <property name="realm" ref="myRealm" />           <property name="sessionManager" ref="sessionManager"/>         <property name="cacheManager" ref="shiroEhcacheManager"/>    </bean>      <!-- 自定义Realm实现 -->     <bean id="myRealm" class="com.core.shiro.CustomRealm" />          <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">     <property name="prefix" value="/"/>     <property name="suffix" value=".jsp"></property>  </bean></beans>  
ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>  <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"      xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"      updateCheck="false" maxBytesLocalDisk="20G" maxBytesLocalOffHeap="50M">          <diskStore path="java.io.tmpdir"/> <!-- 系统的默认临时文件路径 -->    <defaultCache eternal="false"           maxElementsInMemory="10000"          overflowToDisk="false"           timeToIdleSeconds="0"          timeToLiveSeconds="0"           memoryStoreEvictionPolicy="LFU" />     <!--     eternal:缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。    maxElementsInMemory:缓存中允许创建的最大对象数    overflowToDisk:内存不足时,是否启用磁盘缓存。    timeToIdleSeconds:缓存数据的钝化时间,也就是在一个元素消亡之前,            两次访问时间的最大时间间隔值,这只能在元素不是永久驻留时有效,  如果该值是 0 就意味着元素可以停顿无穷长的时间。    timeToLiveSeconds:缓存数据的生存时间,也就是一个元素从构建到消亡的最大时间间隔值,           这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。    memoryStoreEvictionPolicy:缓存满了之后的淘汰算法。    1 FIFO,先进先出2 LFU,最少被使用,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。3 LRU,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。    -->    <cache name="myCache"  eternal="false"           maxElementsInMemory="10000"          overflowToDisk="false"           timeToIdleSeconds="0"          timeToLiveSeconds="0"           memoryStoreEvictionPolicy="LFU" />      <cache name="shiro-activeSessionCache" eternal="false"          maxElementsInMemory="10000"            overflowToDisk="true"          timeToIdleSeconds="0"          timeToLiveSeconds="0"/></ehcache> 
login.jsp的javascript:

<script type="text/javascript">    function kickout(){       var href=location.href;       if(href.indexOf("kickout")>0){           alert("您的账号在另一台设备上登录,您被挤下线,若不是您本人操作,请立即修改密码!");       }     }    window.onload=kickout(); </script>
KickoutSessionControlFilter

package com.core.shiro;import java.io.Serializable;import java.util.Deque;import java.util.LinkedList;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import org.apache.shiro.cache.Cache;import org.apache.shiro.cache.CacheManager;import org.apache.shiro.session.Session;import org.apache.shiro.session.mgt.DefaultSessionKey;import org.apache.shiro.session.mgt.SessionManager;import org.apache.shiro.subject.Subject;import org.apache.shiro.web.filter.AccessControlFilter;import org.apache.shiro.web.util.WebUtils;public class KickoutSessionControlFilter  extends AccessControlFilter{private String kickoutUrl; //踢出后到的地址    private boolean kickoutAfter; //踢出之前登录的/之后登录的用户 默认踢出之前登录的用户    private int maxSession; //同一个帐号最大会话数 默认1    private SessionManager sessionManager;    private Cache<String, Deque<Serializable>> cache;    public void setKickoutUrl(String kickoutUrl) {        this.kickoutUrl = kickoutUrl;    }    public void setKickoutAfter(boolean kickoutAfter) {        this.kickoutAfter = kickoutAfter;    }    public void setMaxSession(int maxSession) {        this.maxSession = maxSession;    }    public void setSessionManager(SessionManager sessionManager) {        this.sessionManager = sessionManager;    }    public void setCacheManager(CacheManager cacheManager) {        this.cache = cacheManager.getCache("shiro-activeSessionCache");    }     /**      * 是否允许访问,返回true表示允许      */    @Override    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {    return false;    }    /**     * 表示访问拒绝时是否自己处理,如果返回true表示自己不处理且继续拦截器链执行,返回false表示自己已经处理了(比如重定向到另一个页面)。     */    @Override    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();        // 初始化用户的队列放到缓存里        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.getFirst();                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            e.printStackTrace();            }        }        //如果被踢出了,直接退出,重定向到踢出后的地址        if (session.getAttribute("kickout") != null) {            //会话被踢出了            try {                subject.logout();            } catch (Exception e) {             }            WebUtils.issueRedirect(request, response, kickoutUrl);            return false;        }        return true;    }}


2 0
原创粉丝点击