Apache Shiro深入会话管理篇之下篇:会话集群和会话与主体状态。具体内容详见下文。
1. 会话集群
Apache Shiro的会话功能非常令人兴奋的事情之一是您可以本地集群主体会话,并且再也不用担心如何根据您的容器环境对会话进行集群。 也就是说,如果您使用Shiro的本地会话并配置会话集群,那么您可以在开发中将Jetty或Tomcat部署到JBoss或Geronimo中,或者在其他任何环境中部署 - 一直不会担心容器/特定环境 集群设置或配置。 在Shiro中配置一次会话群集,无论您的部署环境如何,它都可以工作。
那它是怎么工作的呢?
由于Shiro基于POJO的N层体系结构,启用会话群集跟在会话持久化级别启用群集机制一样简单。 也就是说,如果您配置了支持集群的SessionDAO,则DAO可以与集群机制进行交互,而Shiro的SessionManager从不需要知道集群关系问题。
分布缓存
分布式缓存如Ehcache+TerraCotta,GigaSpaces Oracle Coherence和Memcached(以及其他许多应用)已经解决了分布式数据持久化问题。 因此在Shiro中启用会话群集与配置Shiro使用分布式缓存一样简单。
这为您提供了选择适合您的环境的确切群集机制的灵活性。
注意:高速内存缓存
请注意,启用分布式/企业缓存作为会话群集数据存储时,以下两种情况之一必须为真:
● 分布式缓存具有足够的群集范围内存以保留_all_活动的/当前的会话;
● 如果分布式缓存没有足够的群集范围的内存来保留所有活动会话,则它必须支持磁盘溢出,这样会话才不会丢失。
如果缓存无法支持这两种情况,会导致会话随机丢失,这可能会让最终用户感到沮丧。
4.1 企业级缓存会话DAO(EnterpriseCacheSessionDAO)
正如您所预料的那样,Shiro已经提供了SessionDAO实现,它将数据保存到企业级/分布式缓存中。 EnterpriseCacheSessionDAO需要在其上配置Shiro Cache或CacheManager,以便利用缓存机制。Shiro.ini中配置示例如下:
#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实例,但是通常配置一个通用的CacheManager用于Shiro的所有缓存需求(会话以及认证和授权数据)通常要更常见。 在这种情况下,不是直接配置Cache实例,而是告诉EnterpriseCacheSessionDAO 在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! 那么sessionDAO如何使用分布式缓存?
当Shiro初始化SecurityManager时,它将检查SessionDAO是否实现CacheManagerAware接口。 如果有,它将自动提供任何可用的全局配置的CacheManager。
因此,当Shiro评估securityManager.cacheManager = $ cacheManager行时,它会发现EnterpriseCacheSessionDAO实现了CacheManagerAware接口,并使用您配置的CacheManager作为方法参数调用setCacheManager方法。
然后在运行时,当EnterpriseCacheSessionDAO需要activeSessionsCache时,它会要求CacheManager实例返回它,并使用activeSessionsCacheName作为查找键来获取Cache实例。 该Cache实例(由分布式/企业缓存产品的API支持)将用于存储和检索所有SessionDAO CRUD操作的会话。
4.2 Ehcache + Terracotta
人们在使用Shiro的过程中获得成功的一种分布式缓存解决方案是Ehcache + Terracotta配对。 有关如何使用Ehcache启用分布式缓存的完整详细信息,请参阅Ehcache托管的分布式缓存与Terracotta文档(http://www.ehcache.org/documentation/get-started/about-distributed-cache)。
一旦实现Terracotta集群与Ehcache一起工作,Shiro特定的部分非常简单。 阅读并遵循Ehcache SessionDAO(http://shiro.apache.org/session-management.html#SessionManagement-ehcachesessiondao)文档,但我们需要进行一些更改。
先前引用的Ehcache会话缓存配置不起作用 ——需要使用Terracotta特定的配置。 这是一个已经过测试可以正常工作的示例配置。 将其内容保存在文件中并将其保存在ehcache.xml文件中:
maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="false" diskPersistent="false" diskExpiryThreadIntervalSeconds="120"> maxElementsInMemory="10000" eternal="true" timeToLiveSeconds="0" timeToIdleSeconds="0" diskPersistent="false" overflowToDisk="false" diskExpiryThreadIntervalSeconds="600">
|
(
TerraCotta会话集群配置)当然你会想改变你的
在保存了这个ehcache.xml文件后,我们需要在Shiro的配置中引用它。 假设您已经在类路径的根目录下创建了特定于terracotta的ehcache.xml文件,那么这里是最终的Shiro配置,它允许Terracotta+Ehcache集群满足Shiro的所有需求(包括Sessions)
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 |
(shiro.ini中基于Ehcache和Terracotta的会话集群)
请记住,事项顺序。 通过最后在securityManager上配置cacheManager,我们确保CacheManager可以传播到所有以前配置的CacheManagerAware组件(例如EnterpriseCachingSessionDAO)。
4.3 zookeeper
用户已经报告过也使用Apache Zookeeper(http://zookeeper.apache.org/)来管理/协调分布式会话。 如果您有任何关于如何工作的文档/评论,请将它们发布到Shiro邮件列表.
1. 会话和主体状态
5.1 有状态应用(允许会话)
默认情况下,Shiro的SecurityManager实现将使用Subject的会话作为策略来存储Subject的身份标识(PrincipalCollection)和身份验证状态(subject.isAuthenticated())以供持续引用。 这通常发生在主体登录后或通过RememberMe服务发现主体的身份标识。
这种默认方法有几个好处:
●任何为请求、调用或消息提供服务的应用程序都可以将会话ID与请求/调用/消息有效载荷相关联,这就是Shiro将用户与入站请求相关联的必要条件。 例如,如果使用Subject.Builder,这就是获取关联主体所需的全部内容:
Serializable sessionId = //get from the inbound request or remote method invocation payload Subject requestSubject = new Subject.Builder().sessionId(sessionId).buildSubject();
也就是说,这对大多数Web应用程序以及任何编写远程或消息框架的人来说都非常方便。 (事实上,Shiro的Web支持特性,可实现如何将主体与ServletRequests在其自己的框架代码中关联起来)。
●首次访问时,初始请求中发现的任何'RememberMe'身份都可以保留到会话中。 这确保了主体的记忆身份可以保存在各个请求中,而无需在每次请求中进行反序列化和解密。 例如,在Web应用程序中,如果会话中已知身份,则无需在每个请求上读取加密的RememberMe cookie。 这可以是一个很好的性能增强。
5.2 无状态应用(无会话)
虽然上面的默认策略对于大多数应用程序来说都很好(并且通常是可取的),但对于试图尽可能无状态的应用程序来说,这是不可取的。 许多无状态体系结构要求在请求之间无需存在持久状态,在这种情况下,会话将不被允许(会话本质上代表持久状态)。
但是这个要求是以便宜的成本来实现的——主体状态不能保留在跨请求中。 这意味着具有此要求的应用程序必须确保对于每个请求,其主体状态可以用某种其他方式表示。
这几乎总是通过认证由应用程序处理的每个请求/调用/消息来实现的。 例如,大多数无状态Web应用程序通常通过强制执行HTTP基本身份验证来支持此功能,从而允许浏览器代表最终用户对每个请求进行身份验证。 远程处理或消息传递框架必须确保主体要点和凭证被连接到每个调用或消息有效负载,这通常由框架代码执行。
5.2.1 禁用主体状态会话存储
从Shiro1.2和更高版本开始,希望禁用Shiro内部将Subject状态保留到会话的实现策略的应用,可以完全禁用所有主体会话存储,具体做法如下:
(shiro.ini中配置securityManager以下属性实现:)
[main] ... securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled = false ... |
这将阻止Shiro使用Subject的会话在所有主体的请求/调用/消息之间存储该Subject的状态。 只要确保您对每个请求进行身份验证,Shiro就会知道对于任何给定的请求/调用/消息,其Subject是谁。
注意:Shiro需要和你的需要——
这将使Shiro自己的实现无法使用Sessions作为存储策略。 它不会完全禁用会话。 如果您自己的任何代码显式调用subject.getSession()或subject.getSession(true),则会话仍将被创建。
5.3 混合方法
上述shiro.ini配置行(securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled = false)将禁止Shiro使用Session作为所有主题的会话存储实施策略。
但是如果你想要一个混合方法该如何呢? 如果某些主体需要有会话而其他则不要该怎么办? 这种混合方法对许多应用程序都有好处。 例如:
●也许人类主体(例如网络浏览器用户)应该能够使用Sessions来获得上述好处。
●也许非人类主体(例如API客户端或第三方应用程序)不应创建会话,因为它们与软件的交互可能是间歇性的和/或不稳定的。
●也许所有特定类型的主体或从某个位置访问系统的主题体应该在会话中保持状态,但其他所有主体都不应该。
如果你需要这种混合方法,你可以实现一个SessionStorageEvaluator。
5.3.1 SessionStorageEvaluator
如果您想精确控制哪些主体可能在其会话中保持其状态,您可以实现org.apache.shiro.mgt.SessionStorageEvaluator接口并告诉Shiro究竟哪些主题应该支持会话存储。
SessionStorageEvaluator这个接口仅有一个方法,如下所示:
public interface SessionStorageEvaluator { public boolean isSessionStorageEnabled(Subject subject); } |
该接口更多的详细解释请参考“SessionStorageEvaluator JavaDoc”(http://shiro.apache.org/static/current/apidocs/org/apache/shiro/mgt/SessionStorageEvaluator.html)。您可以实现此接口并检查主体以获取您可能需要做出此决定的任何信息。
Subject检查
在实现isSessionStorageEnabled(主体)接口方法时,您可以随时查看主题并获取您需要做出决定的任何内容。 当然,所有预期的Subject方法都可以使用(如getPrincipals()等),但特定于环境的Subject实例也很有用。
例如,在Web应用程序中,如果必须根据当前ServletRequest中的数据做出决定,则可以获取请求或响应,因为运行时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; } |
注: 框架开发人员应该记住这种类型的访问,并确保任何请求/调用/消息上下文对象都可以通过特定于环境的Subject实现来使用。 如果您需要帮助来为您的框架/环境进行设置,请联系Shiro用户邮件列表。
5.3.2 配置
在你实现了SessionStorageEvaluator接口之后,你可以在shiro.ini中配置它,示例如下:
[main] ... sessionStorageEvaluator = com.mycompany.shiro.subject.mgt.MySessionStorageEvaluator securityManager.subjectDAO.sessionStorageEvaluator = $sessionStorageEvaluator ... |
5.4 Web应用
通常,Web应用程序希望简易地基于每个请求启用或禁用会话创建,而不管哪个Subject执行请求。 这通常用于支持REST和Messaging / RMI体系结构。 例如,可能正常的最终用户(使用浏览器的人)可以创建和使用会话,但远程API客户端使用REST或SOAP,根本不应该有会话(因为它们在每个请求上进行身份验证, 这在REST/SOAP体系结构很常见)。
为了支持这种混合/每个请求功能,一个noSessionCreation过滤器已经添加到Shiro为Web应用程序启用的默认过滤器的"池"中。 此过滤器将防止在请求期间创建新会话以保证无状态体验。 在shiro.ini urls部分,您通常将此过滤器定义在所有其他过滤器之前,以确保永远不会使用会话。例如(shiro.ini 每请求中禁用会话创建):
[urls] ... /rest/** = noSessionCreation, authcBasic, ... |
此过滤器允许任何现有会话使用会话,但不允许在过滤的请求期间创建新会话。 也就是说,以下四个方法中的任何一个对尚未拥有现有会话的请求或主体的调用都会自动触发DisabledSessionException:
● httpServletRequest.getSession()
● httpServletRequest.getSession(true)
● subject.getSession()
● subject.getSession(true)
如果一个Subject在访问noSessionCreation-protected-URL之前已经有一个会话,则上述4个调用仍然按预期工作。
最后,在任何情况下都将允许以下调用:
● httpServletRequest.getSession(false)
● subject.getSession(false)
至此,关于Apache Shiro的会话管理就基本讲完了。下次再分享其它相关主题内容。谢谢。
——别忘了关注本号啊,请顺便转发、点赞呗^_^
閱讀更多 3T教育編程猿 的文章