Shiro 会话管理
Apache Shiro 提供了一个非常独特的特性,即完整的企业级会话解决方案
,既适用于最简单的命令行应用程序,也可以用在集群的企业Web应用中。在Shiro之前,如果应用程序需要会话的支持,那么你只能将你的应用部署到Web容器中,或者使用EJB的有状态会话Bean。Shiro提供的会话用起来很简单,而且可以和上述两种会话机制结合使用,不管有没有容器,都可以使用Shiro的会话
即使你将你的应用部署在一个Servlet
或者EJB
容器中,也有足够的理由使用Shiro的会话替代容器提供的会话。以下为Shiro会话的特性:
-
基于POJO/J2SE:Shiro中的一切都是面向接口的,且都是以
POJO
的形式实现的,可以通过任何JavaBeans
兼容的配置格式进行配置,如JSON,YAML,XML
等等,还可以通过继承Shiro
的组件或实现其接口的方式,自定义自己的功能 -
会话存储:因为Shiro的会话对象是基于
POJO
实现的,会话数据可以很容易的保存在各种数据源中,可以自行决定将会话数据的存储方式——文件系统,内存都可以 -
独立于容器的集群:可以使用任何现有的网络缓存产品来对Shiro的会话进行集群管理,如
Ehcache+Terracotta
,Coherence
,GigaSpaces
等等,不管你的应用如何部署,都能以同样的方式部署会话集群 -
支持各种客户端接入:不同于
EJB
和Web
会话,Shiro的会话可以跨客户端共享,如一个桌面应用可以和Web应用共享同一个安全会话 - 事件监听:可以通过配置事件监听器,监听会话的生命周期,在某些事件发生时做出相应的处理,如更新用户数据
- 保留主机地址:Shiro的会话会保留会话创建时对方的IP地址或主机名,我们可以通过这些信息来对用户进行定位,并作出相应的反应
-
会话过期:会话会因为长期的不活动而失效,可以通过
touch()
方法来保持会话的火星。比如用户正在使用桌面应用程序,但没有定期与服务器通信,这时候的服务器会话就不应该失效 -
Web使用:
Shiro
完全实现了Servlet2.5
中的Session
(HttpSession
接口及其所有相关API),这意味着在现有的Web应用中可以使用Shiro而不需要更改任何代码 -
SSO:由于Shiro的会话是基于POJO的,可以轻松存储在任何数据源中,也可以轻松的实现共享。因此,利用Shiro的会话共享机制,可以轻松实现
SSO
使用Session
可以通过当前执行代码的Subject
获取一个Session
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
session.setAttribute("someKey", someValue);
currentUser.getSession()
方法本质上就是currentUser.getSession(true)
的简写
类似于HttpServletRequest
的API,Subject.getSession(boolean create)
方法的功能和HttpServletRequest.getSession(boolean create)
方法是一样的:
- 如果
Subject
已经存在一个Session
,则传入的create
参数将会被忽略,并立即返回Session
- 如果
Subject
不存在Session
,且create
参数为true
,那么一个新的Session
将会被创建并返回 - 如果
Subject
不存在Session
,且create
参数为false
,那么不会创建新Session
,将会返回一个null
getSession()
方法可以在任何应用中工作,包括非Web应用
对于框架开发人员来说,在开发框架代码时,可以使用subject.getSession(false)
来确保不会创建一个不必要的Session
。通过Session
,我们可以设置或者遍历属性值,设置超时时间等等
SessionManager
SessionManager
是负责管理应用中所有subject
的会话的组件,它是直接由SecurityManager
管理的顶级组件。默认情况下,Shiro
使用的是DefaultSessionManager
,它提供了所有的企业级会话管理的特性,如会话校验、失效会话清理等
Web应用使用的
SessionManager
并不是DefaultSessionManager
,详情看Web章节
和其他由SecurityManager
管理的组件一样,SessionManager
也可以使用JavaBean
的配置方式,通过getter/setter
方法来设置其值,例如在shiro.ini
中设置的话
[main]
...
sessionManager = com.foo.my.SessionManagerImplementation
securityManager.sessionManager = $sessionManager
从头实现一个SessionManager
是一个很复杂的工作,估计绝大多数的人都不会想要做这个,Shiro的SessionManager
实现是高度可配置的,通过它的各种配置即可满足系统的多数需求。接下来的文档基于你使用的是Shiro的DefaultSessionManager
实现
Session 超时
默认情况下,Shiro的SessionManager
的实现是当新会话创建时设置30分钟超时
,也就是如果一个会话有30分钟没有任何活动,那么这个会话将会认为是失效了的。可以通过设置SessionManager
的globalSessionTimeout
属性来设置一个全局的超时时间,该值是以毫秒
为单位的值。如下:
[main]
...
# 3,600,000 milliseconds = 1 hour
securityManager.sessionManager.globalSessionTimeout = 3600000
单个会话的超时时间
上述的globalSessionTimeout
值是一个全局的值,所有的会话都会使用这个值,此外,我们还可以为会话单独设置一个超时时间,只需要为会话的timeout
属性设置一个值,和上面一样,这个值是以毫秒为单位的
会话监听器
Shiro支持SessionListener
的概念,在某一些会话事件发生时做出响应。可以通过实现一个SessionListerner
接口或者继承SessionListenerAdapter
的方式,自定义SessionListener
SessionManager
的sessionListeners
属性是一个集合,可以配置一个或多个监听器:
[main]
...
aSessionListener = com.foo.my.SessionListener
anotherSessionListener = com.foo.my.OtherSessionListener
securityManager.sessionManager.sessionListeners = $aSessionListener, $anotherSessionListener, etc.
SessionListener在会响应所有会话的事件
会话存储
会话被创建后,其相关数据需要被存储以便应用程序之后能够使用。SessionManager
会将会话的CRUD
操作委托给它的一个内部组件,SessionDAO
。通过实现SessionDAO
,你可以将你的会话数据存储在任何数据源,如内存,文件系统,关系型数据库等。配置SessionDAO
如下
[main]
...
sessionDAO = com.foo.my.SessionDAO
securityManager.sessionManager.sessionDAO = $sessionDAO
Shiro内置了一些SessionDAO
的实现,可以直接使用,或继承它们定制一些自己的功能
securityManager.sessionManager.sessionDAO = $sessionDAO
,这个配置只在使用Shiro的原生会话管理器的时候有效,web应用默认使用的是Servlet容器的会话管理器,而不是Shiro原生的,这是不支持SessionDAO
的。如果想要在web应用中使用SessionDAO
的功能,那就需要配置一个web 会话管理器[main] ... sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager securityManager.sessionManager = $sessionManager # Configure a SessionDAO and then set it: securityManager.sessionManager.sessionDAO = $sessionDAO
Shiro原生会话管理器,默认使用的是将会话保存在内存中存储机制,但大多数的应用程序或许更希望配置一个
EHCache
缓存机制,或者提供他们自己的SessionDAO
实现。注意web应用程序使用的是Servlet
容器的会话管理,不在这个话题的讨论范围之内
EHCache SessionDAO
EHCache
默认是没有开启的,如果你想要实现自己的SessionDAO
,那么强烈推荐开启EHCaceh
支持,EHCache
的SessionDAO
将会把会话存储在内存中,如果内存不够时,会把数据迁移到磁盘上
EHCache
不仅可以作用到Session
上,也可以作用到认证和授权数据,详情查Caching的章节
如果想要部署一个独立于容器的会话集群,EHCache也是个不错的选择,也可以在EHCache之后加上TerraCotta,实现一个集群会话的缓存
为会话开启EHCache
是非常简单的,首先,确保应用程序类路径下有shiro-ehcache-<version>.jar
,下面的配置展示了如何使用EHCache
作为缓存
[main]
sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
securityManager.sessionManager.sessionDAO = $sessionDAO
cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
securityManager.cacheManager = $cacheManager
最后一行的securityManager.cacheManager = $cacheManager
,为Shiro
配置了一个全局的CacheManager
,那些实现了CacheManagerAware
接口的类会感知到这个CacheManager
。在SessionDAO
的实现类中,EnterpriseCacheSessionDAO
实现了CacheManagerAware
接口
在这之后,当SessionManager
需要EnterpriseCacheSessionDAO
去持久化一个Session
时,它将会使用EHCache
的Cache
来存储会话数据
别忘了
SessionDAO
只有当使用Shiro原生的会话管理器时才生效
EHCache 会话缓存项配置
默认情况下,EhCacheManager
会使用Shiro预先定义的一个excache.xml
文件来配置缓存区域和一些必要设置来确保会话能被正确的存储和检索。如果希望改变默认的配置,可以配置自己的ehcache.xml
或者自己创建net.sf.ehcache.CacheManager
实例,特别注意要配置缓存区域,确保会话得到正确处理
如果你打开Shiro
默认的ehcache.xml
文件(解压缩先前提到的shiro-encache-<version>.jar
就可以看到),你会发现如下配置选项:
<cache name="shiro-activeSessionCache"
maxElementsInMemory="10000"
overflowToDisk="true"
eternal="true"
timeToLiveSeconds="0"
timeToIdleSeconds="0"
diskPersistent="true"
diskExpiryThreadIntervalSeconds="600"/>
如果你想要使用自己的ehcache.xml
配置,请确保定义了与上面一样的缓存项,这是Shiro
需要的。请注意,以下两个选项是非常重要的:
-
overflowToDisk="true"
:它确保了当你的进程没有内存可用时,会话将会被序列化到磁盘上而不会丢失 -
eternal="true"
:它确保了缓存项不会自动过期,因为Shiro会定时清理过期会话,如果没有设置该选项,EHCache
可能在Shiro并不知道的情况下将还存活的会话从缓存中删除
EHCache 会话缓存项命名
EnterpriseCacheSessionDAO
默认会向CacheManager
查询名为“shiro-activeSessionCache
"的缓存项,如上面看到的,这个命名是在ehcache.xml
中配置的
如果想要修改为其他的缓存项,可以通过配置EnterpriseCacheSessionDAO
的activeSessionsCacheName
属性
...
sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
sessionDAO.activeSessionsCacheName = myname
...
当然,一定要确保ehcache.xml
中有对应的缓存项,并且配置了overflowToDisk="true"和eternal="true"
自定义SessionID
在一个新的Session
创建时,Shiro的SessionDAO
会使用内部的SessionIdGenerator
组件去生成一个新的SessionID
,新创建的Session
就会使用这个SessionID
,然后保存在SessionDAO
中。默认的SessionIdGenerator
是JavaUuidSessionIdGenerator
,它会基于Java
的UUIDs
生成一个字符串
如果这个默认的SessionID
生成方式不符合你的需求,可以自定义一个实现了SessionIdGenerator
接口的类,并将其配置到Shiro的SessionDAO
接口中,如下
[main]
...
sessionIdGenerator = com.my.session.SessionIdGenerator
securityManager.sessionManager.sessionDAO.sessionIdGenerator = $sessionIdGenerator
会话校验和清理
Shiro提供了一个会话校验的机制,在该机制下,Shiro会定期检测它存储的会话,以便及时发现并清理失效会话,防止其充满整个会话存储区。由于性能原因,会话只能在他们被用到的时候才能检测其是否过期(如subject.getSession()
),这意味着在没有其他周期性的校验机制时,会话存储区将会塞满孤儿会话,导致正常的会话无法存储。所谓孤儿会话,就是已经过期,用户也不再使用,但仍然存在于会话存储区中的会话
举个例子,假设一个用户登录了你的Web应用程序,其会话也被创建并存储在你指定的会话存储区中。如果用户没有登出就关闭了他们的浏览器,那么他们的会话就会在会话存储区中一直存在,成为一个孤儿会话。SessionManager
将没有办法确定这些用户是否不再使用他们的浏览器,因为这个会话也不再会被访问
如果不处理这些孤儿会话,最终它们会充满整个会话存储区,为了防止这个情况的发生,SessionManager
支持SessionValidationScheduler
的概念,SessionValidationScheduler
负责以固定速率周期性地检测会话是否过期,确保它们能够在必要时被清理掉
默认的SessionValidationScheduler
默认的SessionValidationScheduler
是基于JDK
的ScheduledExecutorService
实现的ExecutorServiceSessionValidationScheduler
,它能控制多久进行一次检测。其默认值是一小时进行一次,可以通过指定一个新的ExecutorServiceSessionValidationScheduler
实例来改变这个速率,该值是以毫秒为单位的:
[main]
...
sessionValidationScheduler = org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler
# Default is 3,600,000 millis = 1 hour:
sessionValidationScheduler.interval = 3600000
securityManager.sessionManager.sessionValidationScheduler = $sessionValidationScheduler
自定义SessionValidationScheduler
如果你想使用自己的SessionValidationScheduler
,可以为SessionManager
实例的sessionValidationScheduler
属性指定一个值
[main]
...
sessionValidationScheduler = com.foo.my.SessionValidationScheduler
securityManager.sessionManager.sessionValidationScheduler = $sessionValidationScheduler
禁用会话校验
在某些情况下,你可能希望禁用掉Shiro的会话校验机制,通过其他方式来清理失效会话,如通过一些缓存产品的TTL机制清理失效会话。可以通过如下配置禁用会话校验
[main]
...
securityManager.sessionManager.sessionValidationSchedulerEnabled = false
通过这个配置可以禁用掉Shiro周期性的校验,但当会话从数据存储中取出来时,仍然会校验其有效性,如果失效,仍然会被清理
如果禁用了
SessionValidationSchedular
,必须通过其他方式来清理过期会话,否则将会让会话存储区充满孤儿会话
删除失效会话
周期性会话校验主要是为了发现所有的失效会话,并将它们从会话存储区中清理掉,默认情况下,只要Shiro检测到一个失效的会话,就会调用SessionDAO.delete(session)
方法删除这个会话
然而,某些应用或许不希望Shiro自动删除失效会话,比如,应用程序中有一个SessionDAO
专门负责查询功能,或许开发团队希望会话在失效之后仍然在一段时间中可用,用于查询过去一周一个用户创建了多少个会话
可以通过以下配置来关闭自动删除失效会话
[main]
...
securityManager.sessionManager.deleteInvalidSessions = false
需要小心的是,如果你关闭这个功能,那么你需要自己负责删除失效会话。此外,即使关闭了这个功能,仍然需要检测会话校验机制的存在——可以是Shiro现存的机制,也可以是自定义的一些机制。这个校验机制将会更新你的会话记录,以表示其是否失效
如果你禁用了Shiro的自动删除失效会话的功能,那你必须负责自己删除失效会话,以确保会话存储区不会被失效的会话充满
注意禁用会话自动删除和禁用会话失效检测是不一样的
会话集群
Apache Shiro的会话其中一个特性就是原生支持集群,不需要关心容器环境如何对会话集群。如果使用Shiro的原生会话进行集群配置,那么不管你的应用是部署在哪,都不用再担心会话的集群配置,一次配置,随处可用
因为Shiro的基于POJO
的N层架构,使得开启会话的集群设置和开启会话持久层的集群机制是一样的,也就是说,如果为SessionManager
设置了一个用于集群的SessionDAO
,那么Shiro的SessionManager
即完成了集群设置,且它不需要知道集群的存在
分布式缓存
现有的分布式缓存产品,如Ehcache+TerraCotta
,GigaSpaces Oracle Coherence
和Memcached
等等,已经解决了在持久层的分布式数据存储的问题了,因此在Shiro中开启会话集群和给Shiro配置使用分布式缓存是一样简单的
在选择具体的集群机制方面,Shiro提供了很大的灵活性
当在会话集群中使用分布式缓存时,下面两个情况必须满足
- 这个分布式缓存必须有足够的内存来保存所有的活跃会话
- 如果分布式缓存中没有足够的内存来保存所有的活跃会话,它必须能够把多余的会话过渡到其他存储中
如果上面的条件不能保证的话,就可能造成会话的随机丢失
EnterpriseCacheSessionDAO
Shiro 内置了一个SessionDAO
的实现类,即EnterpriseCacheSessionDAO
,它能够将数据保存到一个分布式缓存中,它需要Shiro配置的Cache
或CacheManager
才能发挥作用。例如以下配置
#This implementation would use your preferred distributed caching product's APIs:
activeSessionsCache = my.org.apache.shiro.cache.CacheImplementation
sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
sessionDAO.activeSessionsCache = $activeSessionsCache
securityManager.sessionManager.sessionDAO = $sessionDAO
虽然可以像上面那样,为一个SessionDAO
实例直接注入一个Cache
实例,但是更常用的方式是为Shiro配置一个CacheManager
,用于满足Shiro
各个方面的缓存需求,如缓存会话,缓存认证和授权的数据。此时,我们只需要告诉EnterpriseCacheSessionDAO
我们的cache
在CacheManager
中的命名即可
# This implementation would use your caching product's APIs:
cacheManager = my.org.apache.shiro.cache.CacheManagerImplementation
# Now configure the EnterpriseCacheSessionDAO and tell it what
# cache in the CacheManager should be used to store active sessions:
sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
# This is the default value. Change it if your CacheManager configured a different name:
sessionDAO.activeSessionsCacheName = shiro-activeSessionsCache
# Now have the native SessionManager use that DAO:
securityManager.sessionManager.sessionDAO = $sessionDAO
# Configure the above CacheManager on Shiro's SecurityManager
# to use it for all of Shiro's caching needs:
securityManager.cacheManager = $cacheManager
上面例子中,我们并没有告诉SessionDAO
是使用Cache
还是使用CacheManager
,是因为当Shiro初始化一个SecurityManager
时,它会检测每个组件是否实现了CacheManagerAware
接口,如果有,则会自动的将全局配置的CacheManager
实例配置给组件
因此当Shiro执行到securityManager.cacheManager = $cacheManager
这一行时,它会发现EnterpriseCacheSessionDAO
实现了CacheManagerAware
接口,然后调用其setCacheManager
方法,为其设置CacheManager
EnterpriseCacheSessionDAO
也需要前面配置中的activeSessionsCache
类似的Cache
实例,它会向CacheManager
提交它的activeSessionsCacheName
属性值作为查询的键,来获取一个Cache
实例,这个Cache
实例(由你使用的分布式缓存产品提供支持)将会被所有的SessionDAO
用于存储和检索会话
Ehcache + Terracotta
目前人们用的最成功的即使用Ehcache + Terracotta
作为分布式缓存的方式。关于如何用Ehcache
进行分布式缓存的详细信息请看文档Distributed Caching With Terracotta
通过Ehcache+Terracotta
进行Shiro的会话集群设置和上面的EhcacheSessionDAO
小节配置相似,只是有了一些变化
除了之前的Ehcache
会话缓存配置外,我们还需要一些Terracotta
相关的配置。以下是一个配置的例子,已经验证过是可以正常工作的。将下面的内容保存在ehcache.xml
文件中:
<ehcache>
<terracottaConfig url="localhost:9510"/>
<diskStore path="java.io.tmpdir/shiro-ehcache"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120">
<terracotta/>
</defaultCache>
<cache name="shiro-activeSessionCache"
maxElementsInMemory="10000"
eternal="true"
timeToLiveSeconds="0"
timeToIdleSeconds="0"
diskPersistent="false"
overflowToDisk="false"
diskExpiryThreadIntervalSeconds="600">
<terracotta/>
</cache>
<!-- Add more cache entries as desired, for example,
Realm authc/authz caching: -->
</ehcache>
通过<terracottaConfig url="localhost:9510"/>
配置项,就可以为你的Terracotta
服务器配置合适的主机和端口号。还需要注意的是,与之前的配置不同,这里的ehcache-activeSessionCache
元素并没有将diskPersisten
和overflowToDisk
属性设置为true
,因为在集群配置中是不支持这两个属性的true
值的,设置为true
也不会生效
在保存了这个ehcache.xml
配置文件之后,我们需要在Shiro的配置中引入它,假设这个配置文件保存在了项目类路径的根目录下,则最后Shiro的配置如下
sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
# This name matches a cache name in ehcache.xml:
sessionDAO.activeSessionsCacheName = shiro-activeSessionsCache
securityManager.sessionManager.sessionDAO = $sessionDAO
# Configure The EhCacheManager:
cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
cacheManager.cacheManagerConfigFile = classpath:ehcache.xml
# Configure the above CacheManager on Shiro's SecurityManager
# to use it for all of Shiro's caching needs:
securityManager.cacheManager = $cacheManager
注意顺序,这里我们把securityManager.cacheManager = $cacheManager
放在最后一行,是为了确保CacheManager
能够被传递给所有实现了CacheManagerAware
的组件
Zookeeper
也有使用Apache Zookeeper
来管理分布式会话的,不过还没有人上传过相关的文档教程
会话和Subject的状态
有状态应用(允许会话)
Shiro的SecurityManager
的默认实现将会把Subject
的相关信息保存在Subject
的会话中,这通常发生在Subject
登录之后,或通过RememberMe
服务发现Subject
的身份时。这种方式有以下好处:
-
任何服务于
request/invocation/message
的应用程序都可以从request/invocation/message
的有效载荷中获取SessionID
,Shiro通过这个SessionID
将用户和一个入站请求相关联。例如使用Subject.Builder
来获取一个需要的Subject
Serializable sessionId = //get from the inbound request or remote method invocation payload Subject requestSubject = new Subject.Builder().sessionId(sessionId).buildSubject();
在请求中找到的任何
RememberMe
标识都可以在第一次访问时保存到会话中。这确保了Subject
的“记住”标识可以跨请求保存,而不需要对每个请求进行反序列化和解密。例如,在一个web应用程序中,如果在会话中已经知道了Subject的身份标识,就不需要在每个请求上读取加密的RememberMe cookie
。这是一个很好的性能增强
虽然上面的默认策略满足了大部分应用程序的需要,但是,在一些试图成为无状态的应用程序中就不可行了。无状态的架构一般强制要求在各个请求之间不能持有状态,会话本身就是一个状态,因此无状态的架构中一般不允许会话的产生
但是这一要求是以便利性为代价的——Subject
状态不能跨请求保留。这意味着具有此需求的应用程序必须确保每个请求都能以其他方式来表示其Subject
状态
也就是说,对于每一个request/invocation/message
,应用程序都要对其进行认证,例如在大多数无状态的Web应用中,通常要求强制使用HTTP Basic
的方式来进行认证。如果是框架开发者,其远程调用或消息传递框架必须确保将Subject
的principals
和credentials
附加到每个Invocation
或Message
有效负载(通常由框架代码执行)
禁用Subject状态存储到会话中
从Shiro1.2开始,可以完全禁止所有的Subject
把它们的状态保存在会话中,按照如下配置即可:
[main]
...
securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled = false
...
这个配置会防止Shiro使用会话来存储Subject
的状态,但是要确保对每个请求进行身份验证,这样Shiro才会知道是谁发起的request/invocation/message
这个配置只会禁止把
Subject
的数据保存在会话中,但不会完全禁用会话,如果调用了subject.getSession()
或者subject.getSession(true)
方法,仍然会创建一个会话
混合方法
考虑这样的一个情况,应用程序对一部分Subejct
应该可以使用会话,而对于其他Subject
则禁用会话,例如:
- 对于人(如浏览器用户)的
Subject
,需要使用会话来保存Subject
状态 - 对于非人(如第三方应用调用者)则不应该创建会话,因为它们和应用的交互是断断续续的
- 对于某个类型的
Subject
需要将其状态保存在会话中,其他的则不允许
如果想要这样一种混合的方式,则可以实现一个SessionStorageEvaluator
SessionStorageEvaluator
如果你想要精确控制哪些Subject
可以把它们的状态保存在会话中,则可以通过实现org.apache.shiro.mgt.SessionStorageEvaluator
接口来告诉Shiro,这个接口只有一个方法
public interface SessionStorageEvaluator {
public boolean isSessionStorageEnabled(Subject subject);
}
更多详情请看SessionStorageEvaluator的文档
可以实现这个接口,检查传入的subject的信息来决定是否采用会话存储该subject
的状态
Subject检查
当实现这个isSessionStorageEnabled(subject)
方法时,可以从Subject
中获取任何你用来做决策的信息,当然所有的Subject
方法都是可用的,包括一些特定环境下的Subject
的方法也是可用的
例如,在Web应用中,如果需要基于当前ServletRequest
来做决策,可以获得其request
和response
,因为这个Subject
实例实际上就是一个WebSubject
实例
...
public boolean isSessionStorageEnabled(Subject subject) {
boolean enabled = false;
if (WebUtils.isWeb(Subject)) {
HttpServletRequest request = WebUtils.getHttpRequest(subject);
//set 'enabled' based on the current request.
} else {
//not a web request - maybe a RMI or daemon invocation?
//set 'enabled' another way...
}
return enabled;
}
框架开发人员需要牢牢记住这种类型的检查,确保任何request/invocation/message
的上下文对象在特定环境下Subject
的实现里是可用的
配置
在实现了SessionStorageEvaluator
接口之后,就可以将其配置到Shiro中
[main]
...
sessionStorageEvaluator = com.mycompany.shiro.subject.mgt.MySessionStorageEvaluator
securityManager.subjectDAO.sessionStorageEvaluator = $sessionStorageEvaluator
...
Web 应用
Web应用通常会希望无论是哪个Subject
在执行请求,都只需根据每个请求来启用或禁用会话创建,这在常常用来支持那些REST
和Messaging/RMI
架构。例如,允许正常的终端用户(如使用浏览器的人)创建和使用会话,而那些远程API调用者则完全不能使用会话
为了支持这个特性,Shiro提供了一个noSessionCreation
过滤器供Web应用使用,这个过滤器将会防止会话的创建来确保一个无状态的体验。可以在shiro.ini
文件的[urls]
小节进行这些配置
[urls]
...
/rest/** = noSessionCreation, authcBasic, ...
这个过滤器允许使用已经存在的会话,但是在这个被过滤的请求中不允许新会话的创建。因此,如果当前请求没有之前存在的会话,调用下面的方法将会触发一个DisabledSessionException
- httpServletRequest.getSession()
- httpServletRequest.getSession(true)
- subject.getSession()
- subject.getSession(true)
如果Subject
在访问被noSessionCreation
保护的URL之前已经存在会话,那么上面的四个调用将会正常工作
下面两个调用在任何情况下都会正常工作:
- httpServletRequest.getSession(false)
- subject.getSession()