Apache Shiro 会话管理--官网

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+TerracottaCoherenceGigaSpaces等等,不管你的应用如何部署,都能以同样的方式部署会话集群
  • 支持各种客户端接入:不同于EJBWeb会话,Shiro的会话可以跨客户端共享,如一个桌面应用可以和Web应用共享同一个安全会话
  • 事件监听:可以通过配置事件监听器,监听会话的生命周期,在某些事件发生时做出相应的处理,如更新用户数据
  • 保留主机地址:Shiro的会话会保留会话创建时对方的IP地址或主机名,我们可以通过这些信息来对用户进行定位,并作出相应的反应
  • 会话过期:会话会因为长期的不活动而失效,可以通过touch()方法来保持会话的火星。比如用户正在使用桌面应用程序,但没有定期与服务器通信,这时候的服务器会话就不应该失效
  • Web使用Shiro完全实现了Servlet2.5中的SessionHttpSession接口及其所有相关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分钟没有任何活动,那么这个会话将会认为是失效了的。可以通过设置SessionManagerglobalSessionTimeout属性来设置一个全局的超时时间,该值是以毫秒为单位的值。如下:

[main]
...
# 3,600,000 milliseconds = 1 hour
securityManager.sessionManager.globalSessionTimeout = 3600000

单个会话的超时时间

上述的globalSessionTimeout值是一个全局的值,所有的会话都会使用这个值,此外,我们还可以为会话单独设置一个超时时间,只需要为会话的timeout属性设置一个值,和上面一样,这个值是以毫秒为单位的

会话监听器

Shiro支持SessionListener的概念,在某一些会话事件发生时做出响应。可以通过实现一个SessionListerner接口或者继承SessionListenerAdapter的方式,自定义SessionListener

SessionManagersessionListeners属性是一个集合,可以配置一个或多个监听器:

[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支持,EHCacheSessionDAO将会把会话存储在内存中,如果内存不够时,会把数据迁移到磁盘上

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时,它将会使用EHCacheCache来存储会话数据

别忘了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中配置的

如果想要修改为其他的缓存项,可以通过配置EnterpriseCacheSessionDAOactiveSessionsCacheName属性

...
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中。默认的SessionIdGeneratorJavaUuidSessionIdGenerator,它会基于JavaUUIDs生成一个字符串

如果这个默认的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是基于JDKScheduledExecutorService实现的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+TerraCottaGigaSpaces Oracle CoherenceMemcached等等,已经解决了在持久层的分布式数据存储的问题了,因此在Shiro中开启会话集群和给Shiro配置使用分布式缓存是一样简单的

在选择具体的集群机制方面,Shiro提供了很大的灵活性

当在会话集群中使用分布式缓存时,下面两个情况必须满足

  • 这个分布式缓存必须有足够的内存来保存所有的活跃会话
  • 如果分布式缓存中没有足够的内存来保存所有的活跃会话,它必须能够把多余的会话过渡到其他存储中

如果上面的条件不能保证的话,就可能造成会话的随机丢失

EnterpriseCacheSessionDAO

Shiro 内置了一个SessionDAO的实现类,即EnterpriseCacheSessionDAO,它能够将数据保存到一个分布式缓存中,它需要Shiro配置的CacheCacheManager才能发挥作用。例如以下配置

#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我们的cacheCacheManager中的命名即可

# 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元素并没有将diskPersistenoverflowToDisk属性设置为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的方式来进行认证。如果是框架开发者,其远程调用或消息传递框架必须确保将Subjectprincipalscredentials附加到每个InvocationMessage有效负载(通常由框架代码执行)

禁用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来做决策,可以获得其requestresponse,因为这个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在执行请求,都只需根据每个请求来启用或禁用会话创建,这在常常用来支持那些RESTMessaging/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()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容