SessionManager是在应用程序中为所有Subject提供Session的管理,包括创建,删除,失效及验证等。同其的核心组件一样,SessionManager 也是一个由SecurityManager 维护的顶级组件。
在Shiro中默认提供了一个SessionManager的实现DefaultSessionManager。DefaultSessionManager 提供一个应用程序所需的所有企业级会话管理。可以在任何应用程序中使用。
如果想自定义一个SessionManager,可在Shiro.ini中配置。例如:
[main]
…
sessionManager = com.foo.my.SessionManagerImplementation
securityManager.sessionManager = $sessionManager
在系统中SessionManager 可以通过JavaBean 风格的getter/setter 方法获取或设置(getSessionManager()/setSessionManager())。
下面介绍默认的SessionManager包含的一些额配置项:
Session Timeout
默认是30 分钟。也就是说,如果任何Session 创建后闲置(未被使用,未被更新)的时间超过了30 分钟,那么该Session 就被认为是过期的,且不允许再被使用。
你可以通过globalSessionTimeout 属性来为所有的会话定义默认的超时时间。例如,如果你想超时时间是一个小时而不是30 分钟:
[main]
…
# 3,600,000 milliseconds = 1 hour
securityManager.sessionManager.globalSessionTimeout = 3600000
Per-Session Timeout
上面的globalSessionTimeout 值默认是为所有新建的Session 使用的。你可以在每一个会话的基础上控制超时时间通过设置单独的会话超时时间值。与上面的globalSessionTimeout 一样,该值以毫秒(不是秒)为时间单位。
Session Listeners
Shiro 支持SessionListener 概念来允许你对发生的重要会话作出反应。你可以实现SessionListener 接口(或扩展易用的SessionListenerAdapter)来与相应的会话操作作出反应。
由于默认的SessionManager sessionListeners 属性是一个集合,你可以对SessionManager 配置一个或多个listener 实现,就像其他在shiro.ini 中的集合一样:
[main]
…
aSessionListener = com.foo.my.SessionListener
anotherSessionListener = com.foo.my.OtherSessionListener
securityManager.sessionManager.sessionListeners = $aSessionListener, $anotherSessionListener, etc.
当任何会话事件发生时,SessionListeners 都会被通知——不仅仅是对一个特定的会话。
Session Storage
每当一个会话被创建或更新时,它的数据需要持久化到一个存储位置以便它能够被稍后的应用程序访问。同样地,当一个会话失效且不再被使用时,它需要从存储中删除以便会话数据存储空间不会被耗尽。SessionManager 实现了这些Create/Read/Update/Delete(CRUD)操作做为内部组件,同时,通过SessionDAO反映了数据访问对象(DAO)设计模式。
SessionDAO 的作用是你能够通过该接口来与你想要的任何数据存储进行通信。这意味着你的会话数据可以驻留在内存中,文件系统,关系数据库或NoSQL 的数据存储,或其他任何你需要的位置。
你可以将任何SessionDAO 实现作为一个属性配置在默认的SessionManager 实例上。例如,在shiro.ini 中:
[main]
…
sessionDAO = com.foo.my.SessionDAO
securityManager.sessionManager.sessionDAO = $sessionDAO
Shiro 已经有一些很好的SessionDAO 实现,你可以立即使用或实现你需要的子类。
EHCache SessionDAO
EHCache 默认是没有启用的,但如果你不打算实现你自己的SessionDAO,那么强烈地建议你为Shiro 的SessionManagerment 启用EHCache 支持。EHCache SessionDAO 将会在内存中保存会话,并支持若内存不足时可溢出到磁盘。这对确保你在运行时不会随机地“丢失”会话是非常好的。
为所有Shiro 的缓存需要使用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。该CacheManager 实例会自动地直接传送到SessionDAO(通过EnterpriseCacheSessionDAO 实现CacheManagerAware 接口的性质)。
然后,当SessionManager 要求EnterpriseCacheSessionDAO 去持久化一个Session 时,它使用一个EHCache 支持的Cache实现去存储Session 数据。
Custom Session IDs
Shiro 的SessionDAO 在创建一个新的会话时使用一个内置的SessionIdGenerator 组件来产生一个新的Session ID。该ID 生成后,被指派给新近创建的Session 实例,然后该Session 通过SessionDAO 被保存下来。
默认的SessionIdGenerator 是一个JavaUuidSessionIdGenerator,它能产生基于Java UUIDs 的String IDs。该实现能够支持所有的生产环境。
如果它不符合你的需要,你可以实现SessionIdGenerator 接口并在Shiro 的SessionDAO 实例上配置该实现。例如,在shiro.ini 中:
[main]
...
sessionIdGenerator = com.my.session.SessionIdGenerator
securityManager.sessionManager.sessionDAO.sessionIdGenerator = $sessionIdGenerator
Session Validation & Scheduling
Sessions 必须被验证,这样任何无效(过期或停止)的会话能够从会话数据存储中删除。这保证了数据存储不会由于不能再次使用的会话而导致写入超时。
由于性能上的原因,仅仅在Sessions 被访问(也就是subject.getSession())时验证它们是否停止或过期。这意味着,如果没有额外的定期验证,Session orphans(孤儿)将会开始填充会话数据存储。
会话孤儿,如果它们没有定期清除,将会填充会话数据存储(这是很糟糕的)。因此,为了防止丢放孤儿,SessionManager 实现支持SessionValidationScheduler 的概念。SessionValidationScheduler 负责定期地验证会话以确保它们是否需要清理。
Default SessionValidationScheduler
默认可用的SessionValidationScheduler 在所有环境中都是ExecutorServiceSessionValidationScheduler,它使用JDK ScheduledExecutorService 来控制验证频率。
默认地,该实现每小时执行一次验证。你可以通过指定一个新的ExecutorServiceSessionValidationScheduler 实例并指定不同的间隔(以毫秒为单位)改变速率来更改验证频率:
[main]
…
sessionValidationScheduler = org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler
# Default is 3,600,000 millis = 1 hour:
sessionValidationScheduler.interval = 3600000
securityManager.sessionManager.sessionValidationScheduler = $sessionValidationScheduler
Custom SessionValidationScheduler
如果你希望提供一个自定义的SessionValidationScheduler 实现,你可以指定它作为默认的SessionManager 实例的一个属性。例如,在shiro.ini 中:
[main]
…
sessionValidationScheduler = com.foo.my.SessionValidationScheduler
securityManager.sessionManager.sessionValidationScheduler = $sessionValidationScheduler
Disabling Session Validation
在某些情况下,你可能希望禁用会话验证项,由于你建立了一个超出了Shiro 控制的进程来为你执行验证。例如,也许你正在使用一个企业的Cache 并依赖于缓存的Time To Live 设置来自动地去除旧的会话。或者也许你已经制定了一个计划任务来自动清理一个自定义的数据存储。在这些情况下你可以关掉session validation scheduling:
[main]
…
securityManager.sessionManager.sessionValidationSchedulerEnabled = false
当会话从会话数据存储取回数据时它仍然会被验证,但这会禁用掉Shiro 的定期验证。
Enable Session Validation somewhere
如果你关闭了Shiro 的session validation scheduler,你必须通过其他的机制(计划任务等)来执行定期的会话验证。这是保证会话孤儿不会填充数据存储的唯一方法。
Invalid Session Deletion
正如我们上面所说的,进行定期的会话验证主要目的是为了删除任何无效的(过期或停止)会话来确保它们不会填充会话数据存储。
默认地,某些应用程序可能不希望Shiro 自动地删除会话。例如,如果一个应用程序已经提供了一个SessionDAO 备份数据存储查询,也许是应用程序团队希望旧的或无效的会话在一定的时间内可用。这将允许团队对数据存储运行查询来判断,例如,在上周某个用户创建了多少个会话,或一个用户会话的持续时间,或与之类似报告类型的查询。
在这些情形中,你可以关闭invalid session deletion 项。例如,在shiro.ini 中:
[main]
…
securityManager.sessionManager.deletInvalidSessions = false
请注意!如果你关闭了它,你得为确保你的会话数据存储不耗尽它的空间复杂。你必须自己从你的数据存储中删除无效的会话!
还要注意,即使你阻止了Shiro 删除无效的会话,你仍然应该使用某种会话验证方式——要没通过Shiro 的现有验证机制,要么通过一个你自己提供的自定义的机制(见上述的"Disabling Session Validation"获取更多)。验证机制将会更新你的会话记录以反映无效的状态(例如,什么时候它是无效的,它最后一次被访问是什么时候,等等),即使你在其他的一些时间将手动删除它们。
如果你配置Shiro 来让它不会删除无效的会话,你得为确保你的会话数据存储不会耗尽它的空间负责。你必须亲自从你的数据存储删除无效的会话!
另外请注意,禁用会话删除并不等同于禁用session validation schedule(会话验证调度)。你应该总是使用一个会话验证调度机制——无论是Shiro 直接支持或者是你自己的。