Apache Shiro 提供安全框架界独一无二的东西:一个完整的企业级 Session 解决方案,从最简单的命令行及智能手机 应用到最大的集群企业 Web 应用程序。 这对许多应用有着很大的影响——直到 Shiro 出现,如果你需要 session 支持,你需要部署你的应用程序到 Web 容 器或使用 EJB 有状态会话 Bean。Shiro 的 Session 支持比这两种机制的使用和管理更为简单,而且它在适用于任何程序,不论容器。 即使你在一个 Servlet 或 EJB 容器中部署你的应用程序,仍然有令人信服的理由来使用 Shiro 的 Session 支持而不是容 器的。下面是一个 Shiro 的 Session 支持的最可取的功能列表:
Features
(1)POJO/J2SE based(IoC friendly)
- Shiro 的一切(包括所有 Session 和 Session Management 方面)都是基于接口和 POJO 实现。这可以让你轻松地配置所有拥有任何 JavaBeans 兼容配置格式(如 JSON,YAML,Spring XML 或类 似的机制)的会话组件。你也可以轻松地扩展 Shiro 的组件或编写你自己所需的来完全自定义 session management。
(2)Easy Custom Session Storage
- 因为 Shiro 的 Session 对象是基于 POJO 的,会话数据可以很容易地存储在任意 数量的数据源。这允许你自定义你的应用程序会话数据的确切位置——例如,文件系统,联网的分布式缓存, 关系数据库,或专有的数据存储。
(3) Container-Independent Clustering
- Shiro 的会话可以很容易地聚集通过使用任何随手可用的网络缓存产品,像 Ehcache + Terracotta,Coherence,GigaSpaces,等等。这意味着你可以为 Shiro 配置会话群集一次且仅一次, 无论你部署到什么容器中,你的会话将以相同的方式聚集。不需要容器的具体配置!
(4)Heterogeneous Client Access
- 与 EJB 或 web 会话不同,Shiro 会话可以被各种客户端技术“共享”。例如,一 个桌面应用程序可以“看到”和“共享”同一个被使用的物理会话通过在 Web 应用程序中的同一用户。我们 不知道除了 Shiro 以外的其他框架能够支持这一点。
(5) Event Listeners
- 事件监听器允许你在会话生命周期监听生命周期事件。你可以侦听这些事件和对自定义应用 程序的行为作出反应——例如,更新用户记录当他们的会话过期时。
(6)Host Address Retention
– Shiro Sessions 从会话发起地方保留 IP 地址或主机名。这允许你确定用户所在,并作 出相应的反应(通常是在 IP 分配确定的企业内部网络环境)。
(7) Inactivity/Expiration Support
- 由于不活动导致会话过期如预期的那样,但它们可以延续很久通过 touch()方法 来保持它们“活着”,如果你希望的话。这在 RIA(富互联网应用)环境非常有用,用户可能会使用桌面应用程 序,但可能不会经常与服务器进行通信,但该服务器的会话不应过期。
(8) Transparent Web Use - Shiro 的网络支持,充分地实现和支持关于 Sessions(HttpSession 接口和它的所有相关 的 API)的 Servlet2.5 规范.这意味着你可以使用在现有 Web 应用程序中使用 Shiro 会话,并且你不需要改变任 何现有的 Web 代码。 Can be used for SSO - 由于 Shiro 会话是基于 POJO 的,它们可以很容易地存储在任何数据源,而且它们可以跨 程序“共享”如果需要的话。我们称之为"poor man's SSO",并它可以用来提供简单的登录体验,由于共享的 会话能够保留身份验证状态。
一、 Using Sessions
几乎与所有其他在 Shiro 中的东西一样,你通过与当前执行的 Subject 交互来获取 Session:
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
session.setAttribute("someKey", someValue);
subject.getSession()方法是调用 currentUser.getSubject(true)的快捷方式。
对于那些熟悉 HttpServletRequest API 的,Subject.getSession(boolean create)方法与 HttpServletRequest.getSession(boolean create)方法有着异曲同工之效。
(1) 如果该 Subject 已经拥有一个 Session,则 boolean 参数被忽略且 Session 被立即返回。
(2) 如果该 Subject 还没有一个 Session 且 create 参数为 true,则创建一个新的会话并返回该会话。
(3) 如果该 Subject 还没有一个 Session 且 create 参数为 false,则不会创建新的会话且返回 null。
二、Any Application(所有应用)
getSession 要求能够在任何应用程序工作,甚至是非 Web 应用程序。 当开发框架代码来确保一个 Session 没有被创建是没有必要的时候,subject.getSession(false)可以起到很好的作用。 当你获取了一个 Subject 的 Session 后,你可以用它来做许多事情,像设置或取得 attribute,设置其超时时间,以及 更多。请参见 Session 的 JavaDoc 来了解一个单独的会话能够做什么。
三、The SessionManager
SessionManager,名如其意,在应用程序中为所有的 subject 管理 Session—— 创建,删除,inactivity(失效)及验证,等等。如同其他在 Shiro 中的核心结构组件一样,SessionManager 也是一个由 SecurityManager 维护的顶级组件。 默认的 SecurityManger 实现是默认使用立即可用的 DefaultSessionManager。DefaultSessionManager 的实现提供一个 应用程序所需的所有企业级会话管理,如 Session 验证,orphan cleanup,等等。这可以在任何应用程序中使用。
四、Web Applications
Web 应用程序使用不同 SessionManager 实现。请参见 Web 文档获取 web-specific Session Management 信息。 像其他被 SecurityManager 管理的组件一样,SessionManager 可以通过 JavaBean 风格的getter/setter 方法在所有 Shiro 默认 SecurityManager 实现(getSessionManager()/setSessionManager())上获取或设置值。或者例如,如果在使用 shiro.ini 配置:
但从头开始创建一个 SessionManager 是一个复杂的任务且是大多数人不想亲自做的情。Shiro 的立即可用的 SessionManager 实现是高度可定制的和可配置的,并满足大多数的需要。本文档的其余部分假定你将使用 Shiro 的 默认 SessionManager 实现,当覆盖配置选项时。但请注意,你基本上可以创建或插入任何你想要的东西 。
五、Session Timeout
默认地,Shiro 的 SessionManager 实现默认是 30 分钟会话超时。也就是说,如果任何 Session 创建后闲置(未被使 用,它的上次访问时间未被更新)的时间超过了 30 分钟,那么该 Session 就被认为是过期的,且不允许再被使用。
你可以设置 SessionManager 默认实现的 globalSessionTimeout 属性来为所有的会话定义默认的超时时间。例如,如 果你想超时时间是一个小时而不是 30 分钟:
六、 Per-Session Timeout
上面的 globalSessionTimeout 值默认是为新建的 Session 使用的。你可以在每一个会话的基础上控制超时时间通过设 置单独的会话超时时间值。与上面的 globalSessionTimeout 一样,该值以毫秒(不是秒)为时间单位。
七、Session Listeners
Shiro 支持 SessionListener 概念来允许你对发生的重要会话作出反应。你可以实现 SessionListener 接口(或扩展易用 的 SessionListenerAdapter)并与相应的会话操作作出反应。 由于默认的 SessionManager sessionListeners 属性是一个集合,你可以对 SessionManager 配置一个或多个 listener 实 现,就像其他在 shiro.ini 中的集合一样:
八、Configure a SessionDAO
Shiro 的默认配置本地 SessionManagers 使用仅内存 Session 存储。这是不适合大多数应用程序的。大多数生产应用 程序想要配置提供的 EHCache(见下文)支持或提供自己的 SessionDAO 实现。 请注意 Web 应用程序默认使用基于 servlet 容器的 SessionManager,且没有这个问题。这也是使用 Shiro 本地 SessionManager 的唯一问题。
九、EHCache SessionDAO
EHCache 默认是没有启用的,但如果你不打算实现你自己的 SessionDAO,那么强烈地建议你为 Shiro 的 SessionManagerment 启用 EHCache 支持。EHCache SessionDAO 将会在内存中保存会话,并支持溢出到磁盘,若内存 成为制约。这对生产程序确保你在运行时不会随机地“丢失”会话是非常好的。
十、Use EHCache as your default
如果你不准备编写一个自定义的 SessionDAO,则明确地在你的 Shiro 配置中启用 EHCache。EHCache 带来的好处远 不止在 Sessions,缓存验证和授权数据方面。更多信息,请参见 Caching 文档。
十一、Container-Independent Session Clustering
如果你急需独立的容器会话集群,EHCache 会是一个不错的选择。你可以显式地在 EHCache 之后插入 TerraCotta, 并拥有一个独立于容器集群的会话缓存。不必再担心 Tomcat,JBoss,Jetty,WebSphere 或 WebLogic 特定的会话集 群! 为会话启用 EHCache 是非常容易的。首先,确保在你的 classpath 中有 shiro-ehcache-<version>.jar 文件(请参见 Download 页面或使用 Maven 或 Ant+Ivy)。 当在 classpath 中后,这第一个 shiro.ini 实例向你演示怎样为所有 Shiro 的缓存需要(不只是会话支持)使用 EHCache:
最后一行,securityManager.cacheManager = $cacheManager,为所有 Shiro 的需要配置了一个 CacheManager。该 CacheManager 实例会自动地直接传送到 SessionDAO(通过 EnterpriseCacheSessionDAO 实现 CacheManagerAware 接 口的性质)。 然后,当SessionManager要求EnterpriseCacheSessionDAO去持久化一个Session时,它使用一个EHCache支持的 Cache 实现去存储 Session 数据。
十二、 Web Applications
当使用 Shiro 本地的 SessionManager 实现时不要忘了分配 SessionDAO 是一项功能。Web 应用程序默认使用基于容器 的 SessionManager,它不支持 SessionDAO。如果你想在 Web 应用程序中使用基于 EHCache 的会话存储,配置一个 如上所解释的 Web SessionManager。
十三、EHCache Session Cache Configuration
默认地,EhCacheManager 使用一个 Shiro 特定的 ehcache.xml 文件来建立 Session 缓存区以及确保 Sessions 正常存取 的必要设置。 然而,如果你想改变缓存设置,或想配置你自己的 ehcache.xml 或 EHCache net.sf.ehcache.CacheManager 实例,你需 要配置缓存区来确保 Sessions 被正确地处理。 如果你查看默认的 ehcache.xml 文件,你会看到接下来的 shiro-activeSessionCache 缓存配置:
<cache name="shiro-activeSessionCache"
maxElementsInMemory="10000"
overflowToDisk="true"
eternal="true"
timeToLiveSeconds="0"
timeToIdleSeconds="0"
diskPersistent="true"
diskExpiryThreadIntervalSeconds="600"/>
如果你希望使用你自己的 ehcache.xml 文件,那么请确保你已经为 Shiro 所需的定义了一个类似的缓存项。很有可能 你会改变 maxElementsInMemory 的属性值来吻合你的需要。然而,至少下面两个存在于你自己配置中的属性是非常 重要的:
(1) overflowToDisk="true" - 这确保当你溢出进程内存时,会话不丢失且能够被序列化到磁盘上。
(2) eternal="true" - 确保缓存项(Session 实例)永不过期或被缓存自动清除。这是很有必要的,因为 Shiro 基于 计划过程完成自己的验证。如果我们关掉这项,缓存将会在 Shiro 不知道的情况下清扫这些 Sessions,这可能 引起麻烦。
十四、EHCache Session Cache Name
默认地,EnterpriseCacheSessionDAO 向 CacheManager 寻求一个名为"shiro-activeSessionCache"的 Cache。该缓存的 name/region 将在 ehcache.xml 中配置,如上所述。 如果你想使用一个不同的名字而不是默认的,你可以在 EnterpriseCacheSessionDAO 上配置名字,例如:
只要确保在 ehcahe.xml 中有一项与名字匹配且你已经配置好了如上所述的 overflowToDisk="true"和 eternal="true"。
十五、Custom Session IDs
Shiro 的 SessionDAO 实现使用一个内置的 SessionIdGenerator 组件来产生一个新的 Session ID 当每次创建一个新的会 话的时候。该 ID 生成后,被指派给新近创建的 Session 实例,然后该 Session 通过 SessionDAO 被保存下来。 默认的 SessionIdGenerator 是一个 JavaUuidSessionIdGenerator,它能产生基于 Java UUIDs 的 String IDs。该实现能够 支持所有的生产环境。 如果它不符合你的需要,你可以实现 SessionIdGenerator 接口并在 Shiro 的 SessionDAO 实例上配置该实现。例如, 在 shiro.ini 中:
十六、Session Validation & Scheduling
Sessions 必须被验证,这样任何无效(过期或停止)的会话能够从会话数据存储中删除。这保证了数据存储不会由于不 能再次使用的会话而导致写入超时。 由于性能上的原因,仅仅在 Sessions 被访问(也就是 subject.getSession())时验证它们是否停止或过期。这意味着, 如果没有额外的定期验证,Session orphans(孤儿)将会开始填充会话数据存储。
一个常见的说明孤儿的例子是 Web 浏览器中的场景:比方说,用户登录到 Web 应用程序并创建了一个会话来保留 数据(身份验证状态,购物车等)。如果用户不注销,并在应用程序不知道的情况下关闭了浏览器,则他们的会话 实质上是“躺在”会话数据存储的(孤儿)。SessionManager 没有办法检测用户不再使用他们的浏览器,同时该会 话永远不会被再次访问(它是孤儿了)。 会话孤儿,如果它们没有定期 清除,将会填充会话数据存储(这是很糟糕的)。因此,为了防止丢放孤儿, SessionManager 实现支持 SessionValidationScheduler 的概念。SessionValidationScheduler 负责定期地验证会话以确保 它们是否需要清理。
十七、Default SessionValidationScheduler
默认可用的 SessionValidationScheduler 在所有环境中都是 ExecutorServiceSessionValidationScheduler,它使用 JDK ScheduledExecutorService 来控制验证频率。 默认地,该实现每小时执行一次验证。你可以通过指定一个新的 ExecutorServiceSessionValidationScheduler 实例并指 定不同的间隔(以毫秒为单位)改变速率来更改验证频率:
十七、Custom SessionValidationScheduler
如果你希望提供一个自定义的 SessionValidationScheduler 实现,你可以指定它作为默认的 SessionManager 实例的一 个属性。例如,在 shiro.ini 中:
十八、Disabling Session Validation
在某些情况下,你可能希望禁用会话验证项,由于你建立了一个超出了 Shiro 控制的进程来为你执行验证。例如, 也许你正在使用一个企业的 Cache 并依赖于缓存的 Time To Live 设置来自动地去除旧的会话。或者也许你已经制定 了一个计划任务来自动清理一个自定义的数据存储。在这些情况下你可以关掉 session validation scheduling:
当会话从会话数据存储取回数据时它仍然会被验证,但这会禁用掉 Shiro 的定期验证。
十九、Enable Session Validation somewhere
如果你关闭了 Shiro 的 session validation scheduler,你必须通过其他的机制(计划任务等)来执行定期的会话验证。 这是保证会话孤儿不会填充数据存储的唯一方法。
二十、Invalid Session Deletion
正如我们上面所说的,进行定期的会话验证主要目的是为了删除任何无效的(过期或停止)会话来确保它们不会填 充会话数据存储。 默认地,某些应用程序可能不希望 Shiro 自动地删除会话。例如,如果一个应用程序已经提供了一个 SessionDAO 备 份数据存储查询,也许是应用程序团队希望旧的或无效的会话在一定的时间内可用。这将允许团队对数据存储运行 查询来判断,例如,在上周某个用户创建了多少个会话,或一个用户会话的持续时间,或与之类似报告类型的查询。
在这些情形中,你可以关闭 invalid session deletion 项。例如,在 shiro.ini 中:
请注意!如果你关闭了它,你得为确保你的会话数据存储不耗尽它的空间复杂。你必须自己从你的数据存储中删除 无效的会话! 还要注意,即使你阻止了 Shiro 删除无效的会话,你仍然应该使用某种会话验证方式——要没通过 Shiro 的现有验证 机制,要么通过一个你自己提供的自定义的机制(见上述的"Disabling Session Validation"获取更多)。验证机制将会 更新你的会话记录以反映无效的状态(例如,什么时候它是无效的,它最后一次被访问是什么时候,等等),即使 你在其他的一些时间将手动删除它们。 如果你配置 Shiro 来让它不会删除无效的会话,你得为确保你的会话数据存储不会耗尽它的空间负责。你必须亲自 从你的数据存储删除无效的会话! 另外请注意,禁用会话删除并不等同于禁用 session validation schedule(会话验证调度)。你应该总是使用一个会话 验证调度机制——无论是 Shiro 直接支持或者是你自己的。
Sessions and subject State
一、 Stateful Applications(Sessions allowed)
默认地,Shiro 的 SecurityManager 实现使用一个 Subject 的 Session 作为一种策略来为接下来的引用存储 Subject 的身 份 ID(PrincipalCollection)和验证状态(subject.isAuthenticated())。这通常发生在一个 Subject 登录后或当一个 Subject 的身份 ID 通过 Remember 服务被发现后。
下面是使用这种默认方式的好处:
任何服务于请求,调用或消息的应用程序可以用请求/调用/消息的有效载荷关联会话 ID,且这是 Shiro 用入站 请求关联用户所有所必须的。例如,如果使用 Subject.Builder,这是需要获取相关的 Subject 所需的一切: Serializable sessionId = //get from the inbound request or remote method invocation payload Subject requestSubject = new Subject.Builder().sessionId(sessionId),buildSubject();
这给大多数 Web 应用程序及任何编写远程处理或消息框架的人带来了令人难以置信的方便(这事实上是 Shiro 的 Web 支持在自己的框架代码内关联 Subject 和 ServletRequest)。
任何"RememberMe"身份基于一个能够在第一次访问就能持久化到会话的初始请求。这确保了 Subject 被记住 的身份可以跨请求保存而不需要反序列化及将它解释到每个请求。例如,在一个 Web 应用程序中,没有必要 去读取每一个请求的加密 RememberMe Cookie,如果该身份在会话中是已知的。这可是一个很好的性能提升。
这给大多数 Web 应用程序及任何编写远程处理或消息框架的人带来了令人难以置信的方便(这事实上是 Shiro 的 Web 支持在自己的框架代码内关联 Subject 和 ServletRequest)。
任何"RememberMe"身份基于一个能够在第一次访问就能持久化到会话的初始请求。这确保了 Subject 被记住 的身份可以跨请求保存而不需要反序列化及将它解释到每个请求。例如,在一个 Web 应用程序中,没有必要 去读取每一个请求的加密 RememberMe Cookie,如果该身份在会话中是已知的。这可是一个很好的性能提升
二、Stateless Applications(Sessionless)
虽然上述的默认策略对于大多数应用程序而言是很好的(通常是可取的),但这对于尝试尽可能无状态的应用程序 来说是不合适的。许多无状态的架构规定在请求中不能存在持久状态,这种情况下的 Sessions 不会被允许(一个会 话其本质代表了持久状态)。 但这一要求带来一个便利的代价——Subject状态不能跨请求保留。这意味着有这一要求的应用程序必须确保Subject 状态可以在每一个请求中以其他的方式代表。 这几乎总是通过验证每个由应用程序处理的请求/调用/消息来完成的。例如,大多数无状态 Web 应用程序通常支持 这一点通过执行 HTTP 基本验证,允许浏览器验证每一个代表最终用户的请求。远程或消息框架必须确保 Subject 的 身份和凭证连接到每一个调用或消息的有效载荷,通常是由框架代码执行。
三、Disabling Subject State Session Storage
在 Shiro 1.2 及以后开始,应用程序想禁用 Shiro 的内部实现策略——将 Subject 状态持久化到会话,可以禁用所有 Subject 的这一项,通过下面的操作: 在 shiro.ini 中,在 securityManager 上配置下面的属性:
四、Shiro's Needs vs. Your Needs
使用 Sessions 作为存储策略将禁用 Shiro 本身的实现。它没有完全地禁用 Sessions。如果你的任何代码显式地调用 subject.getSession()或 subject.getSession(true),一个 session 仍然会被创建
五、A Hybrid Approach
上面的 shiro.ini 配置中的(securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled = false)这一行将 会禁用 Shiro 为所有的 Subject 使用 Session 作为一种实现策略。
但,如果你想使用混合的方法呢?如果某些对象应该有会话而某些没有?这种混合法方法能够给许多应用程序带来 好处。例如:
1.也许 human Subject(如 Web 浏览器用户)由于上面提供的好处能够使用 Session。 2.也许 non-human Subject(如 API 客户端或第三方应用程序)不应该创建 session 由于它们与软件的交互可能会 间歇或不稳定。
3.也许所有某种确定类型的 Subject 或从某一确定位置访问系统的应该将状态保持在会话中,但所有其他的不应 该。 如果你需要这个混合方法,你可以实现一个 SessionStorageEvaluator
六、SessionStorageEvaluator
在你想究竟控制哪个 Subject 能够在它们的 Session 中保存它们的状态的情况下,你可以实现 org.apache.shiro.mgt.SessionStorageEvaluator 接口,并告诉 Shiro 哪个 Subject 支持会话存储。 该接口只有一个方法:
关于更详细的 API 说明,请参见 SessionStorageEvaluator 的 JavaDoc。 你可以实现这一接口,并检查 Subject,为了你可能做出这一决定的任何信息。
七、Subject Inspection
但实现 isSessionStorageEnabled(subject)接口方法时,你可以一直查看 Subject 并访问任何你需要用来作出决定的东西。 当然所有期望的 Subject 方法都是可用的(getPrincipals()等),但特定环境的 Subject 实例也是有价值的。 例如,在 Web 应用程序中,如果该决定必须基于当前 ServletRequest 中的数据,你可以获取该 request或该 response, 因为运行时的 Subjce 实例实际上就是一个 WebSubject 实例:
八、Web Applications
通常 Web 应用程序希望在每一个请求的基础上容易地启用或禁用会话的创建,不管是哪个 Subject 正在执行请求。 这经常在支持 REST 及 Messaging/RMI 构架上使用来产生很好的效果。例如,也许正常的终端用户(使用浏览器的人) 被允许创建和使用会话,但远程的 API 客户端使用 REST 或 SOAP,不该拥有会话(因为它们在每一个请求上验证, 常见 REST/SOAP 体系结构)。 为了支持这种hybrid/per-request的能力,noSessionCreation过滤器被添加到Shiro的默认为Web应用程序启用的“池”。 该过滤器将会阻止在请求期间创建新的会话来保证无状态的体验。在 shiro.ini 的[urls]项中,你通常定义该过滤器在 所有其它过滤器之前来确保会话永远不会被使用。
这个过滤器允许现有会话的任何会话操作,但不允许在过滤的请求创建新的会话。也就是说,一个请求或没有会话 存在的 Subject 调用下面四个方法中的任何一个时,将会自动地触发一个 DisabledSessionException 异常:
httpServletRequest.getSession()
httpServletRequest.getSession(true)
subject.getSession()
subject.getSession(true)
如果一个 Subject 在访问 noSessionCreation-protected-URL 之前已经有一个会话,则上述的四种调用仍然会如预期般 工作。 最后,在所有情况下,下面的调用将始终被允许:
httpServletRequest.getSession(false)
subject.getSession(false)