Shiro会话

发表时间:2018-03-22 10:18:30 浏览量( 40 ) 留言数( 0 )

学习目标:

1、了解Shiro的Session相关知识

2、了解有关Session的组件


学习过程:

    Shiro提供的会话可以用于JavaSE/JavaEE环境,不依赖于任何底层容器,可以独立使用,是完整的会话模块。也就是说所有的用户的会话信息都可以由Shiro来进行控制,包括会话信息和会话的管理,可以取得的信息可以有用户名、主机名称等等,这所有的信息都可以通过Subject接口取得。

    SessionManager会话管理器管理着应用中所有Subject的会话的创建、维护、删除、失效、验证等工作,是Shiro的核心组件,Shiro提供了三个默认实现: 

  • DefaultSessionManager:DefaultSecurityManager使用的默认实现,用于JavaSE环境; 

  • ServletContainerSessionManager:DefaultWebSecurityManager使用的默认实现,用于Web环境,其直接使用Servlet容器的会话; 

  • DefaultWebSessionManager:用于Web环境的实现,可以替代ServletContainerSessionManager,自己维护着会话,直接废弃了Servlet容器的会话管理。

    顶层组件SecurityManager直接继承了SessionManager,且提供了SessionsSecurityManager实现直接把会话管理委托给相应的SessionManager,DefaultSecurityManager及DefaultWebSecurityManager默认SecurityManager都继承了SessionsSecurityManager。

  如果使用ServletContainerSessionManager进行会话管理,Session的超时依赖于底层Servlet容器的超时时间,可以在web.xml中配置其会话的超时时间(分钟为单位): 

<session-config>  

  <session-timeout>30</session-timeout>  

</session-config>  


1、shiro的session对象和request的session的解惑

   我们使用shiro的过滤器,这个过滤会做了很多操作

<filter-name>shiroFilter</filter-name>

<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

   这个过滤器会把request等对象代理了。在controller中打印了request的类对象,发现request对象是org.apache.shiro.web.servlet.ShiroHttpServletRequest ,很明显,此时的 request 已经被shiro代理过了。

    spring mvc整合shiro后,可以通过两种方式获取到session:通过 Spring mvc中controller 的 request 获取session

Session session = request.getSession();

    而通过 shiro 获取 session

Subject currentUser = SecurityUtils.getSubject();

Session session = currentUser.getSession();


    把这两个对象打印出来看一下

System.out.println(request.getSession());

System.out.println(subject.getSession());

    确实是两个不同的对象,但是如果你细看一下shiro的源代码,我们发现所有与session相关的方法都是通过它内部委托类delegate进行的,可以看到delegate的类型其实也是 org.apache.catalina.session.StandardSessionFacade ,也就是说,两者在操作session时,都是用同一个类型的session。所以事实上使用这两个session操作的是同一个session而已。

    测试一下,使用

request.getSession().setAttribute("testsession", "haha");

    在使用取值,是可以取得出来的

System.out.println(subject.getSession().getAttribute("testsession"));

    虽然如此,但是如果你的项目里面使用了shiro后,都是采用shiro对会话进行管理,不要再使用原始的session了。因为我们下面会配置不一样的SesionDao。会话信息会在其他的数据库中(比如redis数据库),所以我们更多的时候使用DefaultWebSessionManager。

    shiro会话的一些常用方法:

System.out.println("SESSION ID = " + SecurityUtils.getSubject().getSession().getId());
System.out.println("用户名:" + SecurityUtils.getSubject().getPrincipal());
System.out.println("HOST:" + SecurityUtils.getSubject().getSession().getHost());
System.out.println("TIMEOUT :" + SecurityUtils.getSubject().getSession().getTimeout());
System.out.println("START:" + SecurityUtils.getSubject().getSession().getStartTimestamp());
System.out.println("LAST:" + SecurityUtils.getSubject().getSession().getLastAccessTime());


2、会话的超时配置

   默认Shiro的SessionManager实现是30分钟就超时的了。任何Session超过30分钟不适用,那么这个Session就会被认为已过时了,并不会再被允许进行任何的访问。当然你可以设置SessionManager的globalSessionTimeout属性,修改所有的会话的超时时间。如果使用原始的shiro.ini,比如如果先改成一个小时才超时,那么可以这样设置:

# 3,600,000 milliseconds = 1 hour

securityManager.sessionManager.globalSessionTimeout = 3600000

   Spring配置文件的设置方式

   <!-- 定义的是全局的session会话超时时间,此操作会覆盖web.xml文件中的超时时间配置 -->

        <property name="globalSessionTimeout" value="3600000"/>


3、会话监听器 

    和Servlet一样,你也可以监听Session的建立和销毁。会话监听器用于监听会话创建、过期及停止事件: 

Java代码

public class MySessionListener1 implements SessionListener {  
    @Override  
    public void onStart(Session session) {//会话创建时触发  
        System.out.println("MySessionListener1会话创建:" + session.getId());  
    }  
    @Override  
    public void onExpiration(Session session) {//会话过期时触发  
        System.out.println("MySessionListener1会话过期:" + session.getId());  
    }  
    @Override  
    public void onStop(Session session) {//退出/会话过期时触发  
        System.out.println("MySessionListener1会话停止:" + session.getId());  
    }    
}

spring配置文件配置

   <bean id="mySessionListener1" class="com.shiro.myfilter.MySessionListener1"></bean>
   <bean id="mySessionListener2" class="com.shiro.myfilter.MySessionListener2"></bean>
		<!-- 会话管理器 -->
	<bean id="sessionManager"
		class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
		<!-- 定义的是全局的session会话超时时间,此操作会覆盖web.xml文件中的超时时间配置 -->
        <property name="globalSessionTimeout" value="3600000"/>
        <property name="sessionListeners">
            <list>   
                   <ref bean="mySessionListener1"/>  
                   <ref bean="mySessionListener2"/>  
            </list> 
        </property>
	</bean>

测试,启动后台就可以看到输出:

MySessionListener1会话创建:12eeceb1-1969-46a1-8189-704d190fdeff

MySessionListener2会话创建:12eeceb1-1969-46a1-8189-704d190fdeff


4、会话存储/持久化 

  当一个session在建立和更新时,它的数据都需要持久化,这样应用程序才能再次访问到它的数据,同样,当一个session过期了,他也是需要从持久化数据中删除了。SessionManager实现中提供了一些Create/Read/Update/Delete (CRUD)等基本的操作在一个内置的组件中,这个组件就是SessionDAO。

   这个SessionDAO你也可以根据你自己的需要实现数据的相关操作。你的Session数据可以保存在内存中、文件系统中、关系型数据库中或者其他任何的NoSql的数据库中。你完全可以控制这些持久化的行为。

    DefaultSessionManager在创建完session后会调用SessionDAO的相关方法;如保存到关系数据库/文件系统/NoSQL数据库;即可以实现会话的持久化;返回会话ID;


  • AbstractSessionDAO提供了SessionDAO的基础实现,如生成会话ID等;

  • CachingSessionDAO提供了对开发者透明的会话缓存的功能,只需要设置相应的CacheManager即可;

  • MemorySessionDAO直接在内存中进行会话维护;

  • EnterpriseCacheSessionDAO提供了缓存功能的会话维护,默认情况下使用MapCache实现,内部使用ConcurrentHashMap保存缓存的会话。


建立保存Session

Serializable create(Session session);  

//根据sessionId获取会话  

Session readSession(Serializable sessionId) throws UnknownSessionException;  

//更新会话;如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用  

void update(Session session) throws UnknownSessionException;  

//删除会话;当会话过期/会话停止(如用户退出时)会调用  

void delete(Session session);  

//获取当前所有活跃用户,如果用户量多此方法影响性能  

Collection<Session> getActiveSessions();  

2、示例Spring+Shiro+Redis实现session共享

   事实上在分布式环境中我们经常会面临Session共享和Session集群的问题。事实这个并不依赖Shiro实现,但是我们可以利用SessionDao的功能,把Session信息保存到一个集群的Redis中,这样就可以实现Session的共享和集群的功能了。

   关于Redis的集群我们可以参考前面Redis的学习内容,这里我们就不在啰嗦了。我们只需要在自定义的SessionDao中调用Redis的相关方法就可以了。


5、sessionId和cookieId

   

Custom Session IDs

   Shiro得SessionDao实现使用一个内置得SessionId生成器组件去生成一个新得Session ID,在每一次有一个新得会话是都会生成一个新得Session ID。这个ID是分配给一个新的Session实例,同事SessionDao会保存这个Session实例。

  默认的SessionId生成器就是JavaUuidSessionIdGenerator,默认就是使用Java的UUID功能,这种方式可以适应所有的生产环境。当然,如果你觉得并不是你需要得,你也可以实现自己的SessionIdGenerator,同时需要配置到SessionDAO的实例中。

例如: shiro.ini:


Configuring a SessionIdGenerator in shiro.ini

[main]

...

sessionIdGenerator = com.my.session.SessionIdGenerator

securityManager.sessionManager.sessionDAO.sessionIdGenerator = $sessionIdGenerator 


如果使用Spring 配置文件的配置,也可以如下配置:

<!-- 配置Session DAO的操作处理 --> 

<bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO"> 

    <!-- 设置session缓存的名字,这个名字可以任意 --> 

    <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/> 

    <!-- 定义该Session DAO操作中所使用的ID生成器 --> 

    <property name="sessionIdGenerator" ref="sessionIdGenerator"/> 

</bean>


 SessionId生成器并没有定义session与客户端的之间的联系,为了进行有效的session管理所以还需要建立有一个Cookie的操作模版。 shiro默认的处理Cookie是SimpleCookie,我们可以如下的配置

<!-- 配置需要向Cookie中保存数据的配置模版 --> 

 <!-- sessionIdCookie的实现,用于重写覆盖容器默认的JSESSIONID -->  
    <bean id="simpleCookie" class="org.apache.shiro.web.servlet.SimpleCookie">  
        <!-- cookie的name,对应的默认是 JSESSIONID -->  
        <constructor-arg name="name" value="SHAREJSESSIONID" />  
        <!-- jsessionId的path为 / 用于多个系统共享jsessionId -->  
        <property name="path" value="/" />  
        <property name="httpOnly" value="true"/>  
    </bean>


 <!-- 所有的session一定要将id设置到Cookie之中,需要提供有Cookie的操作模版 -->

        <property name="sessionIdCookie" ref="sessionIdCookie"/>

        <!-- 定义sessionIdCookie模版可以进行操作的启用 -->

        <property name="sessionIdCookieEnabled" value="true"/>



6、session的验证和定时清空 

  任何已经确实是失效的(停止或者过期)的Session的数据都必须从Session的数据库中删除了。以保证无效的数据不要占用着session的数据库。最理想的情况下,Session一旦失效就马上回收了,这样我们也就不需要做其他过多的操作了。如果用户在操作完后,能够点击退出操作,那么应用系统是知道用于已经退出了。所以就可以回收Session的数据。但是很多时候用户直接关闭浏览器,这时候应用程序并不知道用户已经退出了,程序还是保存在该Session的数据的。

   幸好SessionManager的实现是支持一个定时验证SessionValidationScheduler的。SessionValidationScheduler负责定时检查一个Session是否需要清空了。默认的SessionValidationScheduler就是已经可以适应所有的环境了。默认清空下SessionValidationScheduler会每小时执行一次验证的。你可以自己根据需要调整这个频率。

例如:

sessionValidationScheduler = org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler

# Default is 3,600,000 millis = 1 hour:

sessionValidationScheduler.interval = 3600000

securityManager.sessionManager.sessionValidationScheduler = $sessionValidationScheduler

使用Spring的配置方式:

<!-- 配置session的定时验证检测程序类,以让无效的session释放 -->
    <bean id="sessionValidationScheduler"
        class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">
        <!-- 设置session的失效扫描间隔,单位为毫秒 -->
        <property name="sessionValidationInterval" value="100000"/>
        <!-- 随后还需要定义有一个会话管理器的程序类的引用 -->
        <property name="sessionManager" ref="sessionManager"/>
    </bean> 
<!-- 定义会话管理器的操作 -->
    <bean id="sessionManager"
        class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <!-- 定义的是全局的session会话超时时间,此操作会覆盖web.xml文件中的超时时间配置 -->
        <property name="globalSessionTimeout" value="1000000"/>
        <!-- 删除所有无效的Session对象,此时的session被保存在了内存里面 -->
        <property name="deleteInvalidSessions" value="true"/>
        <!-- 定义要使用的无效的Session定时调度器 -->
        <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
        <!-- 需要让此session可以使用该定时调度器进行检测 -->
        <property name="sessionValidationSchedulerEnabled" value="true"/>
    </bean>

   如果有需要,你也可以自定义一个SessionValidationScheduler

   例如下面的配置  shiro.ini

...

sessionValidationScheduler = com.foo.my.SessionValidationScheduler

securityManager.sessionManager.sessionValidationScheduler = $sessionValidationScheduler


禁用Session验证

   在某些应用场景下面,你也可以禁用这个定时器的。

    如下面的配置:

securityManager.sessionManager.sessionValidationSchedulerEnabled = false