# 8. Session Management
# 8. Session Management
Apache Shiro 提供安全框架界獨一無二的東西:一個完整的企業級Session 解決方案,從最簡單的命令行及智能手機應用到最大的集群企業Web 應用程序。
這對許多應用有著很大的影響——直到 Shiro 出現,如果你需要 session 支持,你需要部署你的應用程序到 Web 容器或使用EJB 有狀態會話Bean。Shiro 的 Session 支持比這兩種機制的使用和管理更為簡單,而且它在適用于任何程序,不論容器。
即使你在一個 Servlet 或 EJB 容器中部署你的應用程序,仍然有令人信服的理由來使用 Shiro 的Session 支持而不是容器的。下面是一個 Shiro 的 Session 支持的最可取的功能列表:
特性
- **POJO/J2SE based(IoC friendly)** - Shiro 的一切(包括所有Session 和Session Management 方面)都是基于接口和 POJO 實現。這可以讓你輕松地配置所有擁有任何 JavaBeans 兼容配置格式(如JSON,YAML,Spring XML 或類 似的機制)的會話組件。你也可以輕松地擴展 Shiro 的組件或編寫你自己所需的來完全自定義 session management。
- **Easy Custom Session Storage** - 因為Shiro 的Session 對象是基于 POJO 的,會話數據可以很容易地存儲在任意數量的數據源。這允許你自定義你的應用程序會話數據的確切位置——例如,文件系統,聯網的分布式緩存,關系數據庫,或專有的數據存儲。
- **Container-Independent Clustering!** - Shiro 的會話可以很容易地聚集通過使用任何隨手可用的網絡緩存產品,像 Ehcache + Terracotta,Coherence,GigaSpaces,等等。這意味著你可以為Shiro 配置會話群集一次且僅一次,無論你部署到什么容器中,你的會話將以相同的方式聚集。不需要容器的具體配置!
- **Heterogeneous Client Access** - 與 EJB 或 web 會話不同,Shiro 會話可以被各種客戶端技術“共享”。例如,一個桌面應用程序可以“看到”和“共享”同一個被使用的物理會話通過在 Web 應用程序中的同一用戶。我們不知道除了 Shiro 以外的其他框架能夠支持這一點。
- **Event Listeners** - 事件監聽器允許你在會話生命周期監聽生命周期事件。你可以偵聽這些事件和對自定義應用程序的行為作出反應——例如,更新用戶記錄當他們的會話過期時。
- **Host Address Retention** - Shiro Sessions 從會話發起地方保留IP 地址或主機名。這允許你確定用戶所在,并作出相應的反應(通常是在IP 分配確定的企業內部網絡環境)。
- **Inactivity/Expiration Support** - 由于不活動導致會話過期如預期的那樣,但它們可以延續很久通過 touch() 方法來保持它們“活著”,如果你希望的話。這在 RIA (富互聯網應用)環境非常有用,用戶可能會使用桌面應用程序,但可能不會經常與服務器進行通信,但該服務器的會話不應過期。
- **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) 方法有著異曲同工之效。
- 如果該Subject 已經擁有一個Session,則boolean 參數被忽略且Session 被立即返回。
- 如果該Subject 還沒有一個Session 且create 參數為true,則創建一個新的會話并返回該會話。
- 如果該Subject 還沒有一個Session 且create 參數為false,則不會創建新的會話且返回null。
*Any Application 任何應用*
*getSession 要求能夠在任何應用程序工作,甚至是非 Web 應用程序。*
當開發框架代碼來確保一個 Session 沒有被創建是沒有必要的時候,subject.getSession(false) 可以起到很好的作用。 當你獲取了一個 Subject 的 Session 后,你可以用它來做許多事情,像設置或取得 attribute,設置其超時時間,以及 更多。請參見 Session 的 [JavaDoc](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/session/Session.html)來了解一個單獨的會話能夠做什么。
## The SessionManager
SessionManager,名如其意,在應用程序中為所有的 subject 管理Session —— 創建,刪除,inactivity(失效)及驗證,等等。如同其他在Shiro 中的核心結構組件一樣,SessionManager 也是一個由 SecurityManager 維護的頂級組件。
默認的 SecurityManger 實現是默認使用開箱即用的[DefaultSessionManager](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/mgt/DefaultSecurityManager.html)。DefaultSessionManager 的實現提供一個應用程序所需的所有企業級會話管理,如 Session 驗證,orphan cleanup,等等。這可以在任何應用程序中使用。
*Web Applications**Web 應用程序使用不同SessionManager 實現。請參見 [Web](https://github.com/waylau/apache-shiro-1.2.x-reference/blob/master/III.%20Web%20Applications/10.%20Web.md) 文檔獲取web-specific Session Management 信息。*
像其他被 SecurityManager 管理的組件一樣,SessionManager 可以通過 JavaBean 風格的 getter/setter 方法在所有Shiro 默認 SecurityManager 實現(getSessionManager()/setSessionManager())上獲取或設置值。或者例如,如果在使用 shiro.ini 配置:
```
[main]
...
sessionManager = com.foo.my.SessionManagerImplementation
securityManager.sessionManager = $sessionManager
```
但從頭開始創建一個 SessionManager 是一個復雜的任務且是大多數人不想親自做的事情。Shiro 的開箱即用的SessionManager 實現是高度可定制的和可配置的,并滿足大多數的需要。本文檔的其余部分假定你將使用 Shiro 的默認 SessionManager 實現,當覆蓋配置選項時。但請注意,你基本上可以創建或插入任何你想要的東西。
### Session Timeout 超時
默認地,Shiro 的 SessionManager 實現默認是 30 分鐘會話超時。也就是說,如果任何 Session 創建后閑置(未被使用,它的[lastAccessedTime](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/session/Session.html#getLastAccessTime())未被更新)的時間超過了 30 分鐘,那么該 Session 就被認為是過期的,且不允許再被使用。
你可以設置 SessionManager 默認實現的 globalSessionTimeout 屬性來為所有的會話定義默認的超時時間。例如,如果你想超時時間是一個小時而不是 30 分鐘:
```
[main]
...
# 3,600,000 milliseconds = 1 hour
securityManager.sessionManager.globalSessionTimeout = 3600000
```
#### Per-Session Timeout
上面的 globalSessionTimeout 值默認是為新建的 Session 使用的。你可以在每一個會話的基礎上控制超時時間通過設置單獨的會話 [timeout](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/session/Session.html#setTimeout(long))值。與上面的 globalSessionTimeout 一樣,該值以毫秒(不是秒)為時間單位。
### Session Listeners
Shiro 支持 SessionListener 概念來允許你對發生的重要會話作出反應。你可以實現 [SessionListener](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/session/SessionListener.html) 接口(或擴展易用的SessionListenerAdapter )并與相應的會話操作作出反應。 由于默認的 SessionManager sessionListeners 屬性是一個集合,你可以對 SessionManager 配置一個或多個 listener 實 現,就像其他在 shiro.ini 中的集合一樣:
```
[main]
...
aSessionListener = com.foo.my.SessionListener
anotherSessionListener = com.foo.my.OtherSessionListener
securityManager.sessionManager.sessionListeners = $aSessionListener, $anotherSessionListener, etc.
```
*All Session Events 所有Session 事件*
*當任何會話發生事件時,SessionListeners 都會被通知——不僅僅是對一個特定的會話*
### Session Storage 存儲
每當一個會話被創建或更新時,它的數據需要持久化到一個存儲位置以便它能夠被稍后的應用程序訪問。同樣地,當一個會話失效且不再被使用時,它需要從存儲中刪除以便會話數據存儲空間不會被耗盡。SessionManager 實現委托這些 Create/Read/Update/Delete(CRUD) 操作為內部組件,同時,[SessionDAO](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/session/mgt/eis/SessionDAO.html),反映了數據訪問對象([DAO](http://en.wikipedia.org/wiki/Data_access_object))設計模式。
SessionDAO 的權力是你能夠實現該接口來與你想要的任何數據存儲進行通信。這意味著你的會話數據可以駐留在內存中,文件系統,關系數據庫或NoSQL 的數據存儲,或其他任何你需要的位置。你得控制持久性行為。
你可以將任何 SessionDAO 實現作為一個屬性配置在默認的SessionManager 實例上。例如,在shiro.ini 中:
```
[main]
...
sessionDAO = com.foo.my.SessionDAO
securityManager.sessionManager.sessionDAO = $sessionDAO
```
然而,正如你可能期望的那樣,Shiro 已經有一些很好的SessionDAO 實現,你可以立即使用或實現你需要的子類。
*Web Applications*
*上述的 securityManager.sessionManager.sessionDAO = $sessionDAO 作業僅在使用一個本地的 Shiro 會話管理器時才 工作。Web 應用程序默認不會使用本地的會話管理器,而是保持不支持SessionDAO 的 Servlet Container 的默認會話 管理器。如果你想基于 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
```
*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 設置 EHCache 為默認*
*如果你不準備編寫一個自定義的 SessionDAO,則明確地在你的 Shiro 配置中啟用 EHCache。EHCache 帶來的好處遠不止在 Sessions,緩存驗證和授權數據方面。更多信息,請參見 [Caching](../IV.%20Auxiliary%20Support%20%E8%BE%85%E5%8A%A9%E6%94%AF%E6%8C%81/11.%20Caching%20%E7%BC%93%E5%AD%98.md) 文檔。*
*Container-Independent Session Clustering 獨立容器的Session聚類*
\*如果你急需獨立的容器會話集群,EHCache 會是一個不錯的選擇。你可以顯式地在 EHCache 之后插入[TerraCotta](http://www.terracotta.org/),并擁有一個獨立于容器集群的會話緩存。不必再擔心 Tomcat,JBoss,Jetty,WebSphere 或WebLogic 特定的會話集群!
為會話啟用 EHCache 是非常容易的。首先,確保在你的 classpath 中有shiro-ehcache-.jar 文件(請參見 [Download](http://shiro.apache.org/download.html) 頁面或使用 Maven 或 Ant+Ivy)。\*
當在 classpath 中后,這第一個 shiro.ini 實例向你演示怎樣為所有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](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/cache/CacheManagerAware.html) 接口的性質)。
然后,當 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](https://svn.apache.org/repos/asf/shiro/trunk/support/ehcache/src/main/resources/org/apache/shiro/cache/ehcache/ehcache.xml) 文件來建立 Session 緩存區以及確保 Sessions 正常存取的必要設置。
然而,如果你想改變緩存設置,或想配置你自己的 ehcache.xml 或EHCache net.sf.ehcache.CacheManager 實例,你需要配置緩存區來確保Sessions 被正確地處理。
如果你查看默認的 [ehcache.xml](https://svn.apache.org/repos/asf/shiro/trunk/support/ehcache/src/main/resources/org/apache/shiro/cache/ehcache/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 的屬性值來吻合你的需要。然而,至少下面兩個存在于你自己配置中的屬性是非常重要的:
- overflowToDisk="true" - 這確保當你溢出進程內存時,會話不丟失且能夠被序列化到磁盤上。
- eternal="true" - 確保緩存項( Session 實例)永不過期或被緩存自動清除。這是很有必要的,因為 Shiro 基于計劃過程完成自己的驗證。如果我們關掉這項,緩存將會在 Shiro 不知道的情況下清掃這些 Sessions,這可能引起麻煩
#### EHCache Session Cache Name
默認地,EnterpriseCacheSessionDAO 向 CacheManager 尋求一個名為"shiro-activeSessionCache"的 Cache。該緩存的 name/region 將在 ehcache.xml 中配置,如上所述。
如果你想使用一個不同的名字而不是默認的,你可以在EnterpriseCacheSessionDAO 上配置名字,例如:
```
[main]
...
sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
sessionDAO.activeSessionsCacheName = myname
...
```
只要確保在 ehcahe.xml 中有一項與名字匹配且你已經配置好了如上所述的 overflowToDisk="true" 和 eternal="true"。
#### Custom Session IDs
Shiro 的 SessionDAO 實現使用一個內置的 [SessionIdGenerator](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/session/mgt/eis/SessionIdGenerator.html) 組件來產生一個新的 Session ID 當每次創建一個新的會話的時候。該 ID 生成后,被指派給新近創建的Session 實例,然后該Session 通過SessionDAO 被保存下來。
默認的SessionIdGenerator 是一個 [JavaUuidSessionIdGenerator](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/session/mgt/eis/JavaUuidSessionIdGenerator.html),它能產生基于Java [UUIDs](http://download.oracle.com/javase/6/docs/api/java/util/UUID.html) 的 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(孤兒)將會開始填充會話數據存儲。
一個常見的說明孤兒的例子是 Web 瀏覽器中的場景:比方說,用戶登錄到Web 應用程序并創建了一個會話來保留 數據(身份驗證狀態,購物車等)。如果用戶不注銷,并在應用程序不知道的情況下關閉了瀏覽器,則他們的會話 實質上是“躺在”會話數據存儲的(孤兒)。SessionManager 沒有辦法檢測用戶不再使用他們的瀏覽器,同時該會話永遠不會被再次訪問(它是孤兒了)。
會話孤兒,如果它們沒有定期清除,將會填充會話數據存儲(這是很糟糕的)。因此,為了防止丟放孤兒,SessionManager 實現支持 [SessionValidationScheduler](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/session/mgt/SessionValidationScheduler.html) 的概念。SessionValidationScheduler 負責定期地驗證會話以確保 它們是否需要清理。
#### Default SessionValidationScheduler 默認
默認可用的 SessionValidationScheduler 在所有環境中都是[ExecutorServiceSessionValidationScheduler](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/session/mgt/ExecutorServiceSessionValidationScheduler.html),它使用 JDK [ScheduledExecutorService](http://download.oracle.com/javase/6/docs/api/java/util/concurrent/ScheduledExecutorService.html) 來控制驗證頻率。
默認地,該實現每小時執行一次驗證。你可以通過指定一個新的 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 刪除無效的Session
正如我們上面所說的,進行定期的會話驗證主要目的是為了刪除任何無效的(過期或停止)會話來確保它們不會占用會話數據存儲。
默認地,某些應用程序可能不希望 Shiro 自動地刪除會話。例如,如果一個應用程序已經提供了一個 SessionDAO 備份數據存儲查詢,也許是應用程序團隊希望舊的或無效的會話在一定的時間內可用。這將允許團隊對數據存儲運行查詢來判斷,例如,在上周某個用戶創建了多少個會話,或一個用戶會話的持續時間,或與之類似報告類型的查詢。
在這些情形中,你可以關閉 invalid session deletion 項。例如,在shiro.ini 中:
```
[main]
...
securityManager.sessionManager.deleteInvalidSessions = false
```
請注意!如果你關閉了它,你得為確保你的會話數據存儲不耗盡它的空間。你必須自己從你的數據存儲中刪除無效的會話!
還要注意,即使你阻止了 Shiro 刪除無效的會話,你仍然應該使用某種會話驗證方式——要沒通過 Shiro 的現有驗證機制,要么通過一個你自己提供的自定義的機制(見上述的"Disabling Session Validation"獲取更多)。驗證機制將會更新你的會話記錄以反映無效的狀態(例如,什么時候它是無效的,它最后一次被訪問是什么時候,等等),即使你在其他的一些時間將手動刪除它們。
*如果你配置 Shiro 來讓它不會刪除無效的會話,你得為確保你的會話數據存儲不會耗盡它的空間負責。你必須親自從你的數據存儲刪除無效的會話! 另外請注意,禁用會話刪除并不等同于禁用 session validation schedule(會話驗證調度)。你應該總是使用一個會話驗證調度機制——無論是 Shiro 直接支持或者是你自己的。*
## Session Clustering 會話集群
Apache Shiro 會話能力一個非常令人興奮的事情是,你可以原生的集群 Subject 會話,不需要再擔心你的容器環境。也就是說,如果您使用 Shiro 的原生會話并配置一個會話集群,可以,說,部署到 Jetty 和 Tomcat 開發環境,JBoss 或 Geronimo 的生產環境,或任何其他環境,不用擔心容器/特定于環境的集群安裝或配置。 Shiro 會話集群配置一次,無論您的部署環境如何,都能正常運行
因為 Shiro 的基于 pojo 的 n 層體系結構,使會話集群的集群機制非常簡單,使會話持久性的水平。 也就是說,如果您配置集群 SessionDAO ,DAO 可以與集群交互機制, Shiro 的 SessionManager 不需要知道集群的問題。
### Distributed Caches 分布式緩存
分布式緩存比如 [Ehcache + TerraCotta](http://ehcache.org/documentation/get-started/about-distributed-cache) , [GigaSpaces](http://www.gigaspaces.com/) [Oracle Coherence](http://www.oracle.com/technetwork/middleware/coherence/overview/index.html) , [memcached](http://memcached.org/) (和許多其他)已經解決 distributed-data-at-the-persistence-level(分布式數據持久層) 問題。 因此在 Shiro 會話使用集群配置如同使用分布式緩存一樣簡單。
這使您的靈活性選擇確切的集群機制,適用于你的環境。
*緩存*
*請注意,當啟用分布式/企業緩存在您的會話集群數據存儲,下列兩種情況之一必須是 true 的:*
- *整個集群范圍的分布式緩存有足夠內存來保留所有的 活動/當前 會話*
- *如果整個集群范圍的分布式緩存沒有足夠的內存保留所有活動會話,它必須支持磁盤溢出,會話是不會丟失。*
*如果這兩種情況都失敗,會導致會話被隨機丟失,對于終端用戶來說這可能令人沮喪。*
### EnterpriseCacheSessionDAO
如您所料,Shiro 已經提供了 SessionDAO 的實現,將保存數據到 企業/分布式緩存。 [EnterpriseCacheSessionDAO](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/session/mgt/eis/EnterpriseCacheSessionDAO.html) 預計 Shiro 緩存 或 緩存管理器已經配置,所以它可以利用緩存機制。
例如,在 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 如上所示,它通常是更常見的配置一般是緩存管理器 使用 Shiro 的所有緩存的需求(會話以及身份驗證和授權數據)。 在這種情況下,而不是 直接配置 緩存實例,您會告訴 EnterpriseCacheSessionDAO 緩存管理器 中的緩存的名稱,應該用于存儲活動會話。 例如:
```
# 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 實例使用一個 緩存 或 緩存管理器 ! 所以如何 SessionDAO 使用分布式緩存嗎?
當 Shiro 初始化 SecurityManager ,它將檢查看看 SessionDAO 是否 實現了 [CacheManagerAware](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/cache/CacheManagerAware.html) 接口。 如果是這樣,它將自動提供任何可用的全局配置 緩存管理器 。
所以,當 Shiro 評估 securityManager.cacheManager = $cacheManager 行,它就會發現 EnterpriseCacheSessionDAO 實現了 CacheManagerAware 接口和調用 setCacheManager 和你的配置方法 緩存管理器 作為方法的參數。
然后在運行時,當 EnterpriseCacheSessionDAO 需要 activeSessionsCache 它會問 緩存管理器 使用實例返回它 activeSessionsCacheName 作為查找得到的關鍵 緩存 實例。 那 緩存 實例(支持分布式緩存/企業產品的API)將用于存儲和檢索會話的所有 SessionDAO CRUD 操作。
### Ehcache + Terracotta
這樣一個分布式緩存解決方案,人們取得了成功在使用 Shiro 的Ehcache + Terracotta 配對。 看到 Ehcache-hosted [分布式緩存與Terracotta](http://ehcache.org/documentation/get-started/about-distributed-cache)文檔的全部細節和 Ehcache 如何啟用分布式緩存。
一旦你得到 Terracotta 集群處理 Ehcache ,Shiro-specific 部分非常簡單。 閱讀并遵守 EHCache SessionDAO 文檔,但是我們需要做出一些改變
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>
```
當然你想要改變你 e 條目引用適當的主機/端口 Terracotta 服務器陣列。 還要注意,與 以前的 配置中, ehcache-activeSessionCache 元素不設置 diskPersistent 或 overflowToDisk 屬性為 true 的。他們都應該是 假 作為真實值不支持在集群配置。
在你保存了 ehcache.xml 文件,我們需要在 Shiro 的配置中引用它。 假設你已經作了 terracotta 特定 ehcache.xml 文件在類路徑的根,這是最后的 Shiro 配置,使 Terracotta + Ehcache 集群的 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 最后,我們確保可以傳播到所有之前配置緩存管理器 CacheManagerAware 組件(如 EnterpriseCachingSessionDAO )。
### Zookeeper
用戶報告使用 [Apache Zookeeper](http://zookeeper.apache.org/) 來管理/協調分布式會話。 如果你有任何文檔/評論關于這個工作,請提交到到 [Shiro 郵件列表](http://shiro.apache.org/mailing-lists.html)
## 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,如果該身份在會話中是已知的。這可是一個很好的性能提升。
### Stateless Applications (Sessionless) 無狀態
雖然上述的默認策略對于大多數應用程序而言是很好的(通常是可取的),但這對于嘗試盡可能無狀態的應用程序來說是不合適的。許多無狀態的架構規定在請求中不能存在持久狀態,這種情況下的 Sessions 不會被允許(一個會話其本質代表了持久狀態)。
但這一要求帶來一個便利的代價—— Subject 狀態不能跨請求保留。這意味著有這一要求的應用程序必須確保 Subject 狀態可以在每一個請求中以其他的方式代表。
這幾乎總是通過驗證每個由應用程序處理的請求/調用/消息來完成的。例如,大多數無狀態 Web 應用程序通常支持這一點通過執行 HTTP 基本驗證,允許瀏覽器驗證每一個代表最終用戶的請求
#### Disabling Subject State Session Storage 禁用 Subject 狀態會話存儲
在 Shiro 1.2 及以后開始,應用程序想禁用 Shiro 的內部實現策略——將Subject 狀態持久化到會話,可以禁用所有 Subject 的這一項,通過下面的操作:
在shiro.ini 中,在 securityManager 上配置下面的屬性:
```
[main]
...
securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled = false
...
```
這將防止 Shiro 使用 Subject 的會話來存儲所有跨請求/調用/消息的Subject 狀態。只要確保你對每個請求進行了身份驗證,這樣 Shiro 將會對給定的請求/調用/消息知道它的 Subject 是誰。
*Shiro的需求 vs. 你的需求*
*使用 Sessions 作為存儲策略將禁用 Shiro 本身的實現。它沒有完全地禁用 Sessions。如果你的任何代碼顯式地調用 subject.getSession() 或 subject.getSession(true) ,一個session 仍然會被創建。*
### A Hybrid Approach 一個混合的方法
上面的shiro.ini 配置中的(securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled = false) 這一行將會禁用Shiro 為所有的Subject 使用Session 作為一種實現策略。
但,如果你想使用混合的方法呢?如果某些對象應該有會話而某些沒有?這種混合法方法能夠給許多應用程序帶來好處。例如:
- 也許 human Subject(如 Web 瀏覽器用戶)由于上面提供的好處能夠使用Session。
- 也許non-human Subject(如 API 客戶端或第三方應用程序)不應該創建session 由于它們與軟件的交互可能會 間歇或不穩定。
- 也許所有某種確定類型的 Subject 或從某一確定位置訪問系統的應該將狀態保持在會話中,但所有其他的不應 該。 如果你需要這個混合方法,你可以實現一個 SessionStorageEvaluator。
#### SessionStorageEvaluator
在你想究竟控制哪個 Subject 能夠在它們的 Session 中保存它們的狀態的情況下,你可以實現
org.apache.shiro.mgt.SessionStorageEvaluator 接口,并告訴Shiro 哪個 Subject 支持會話存儲。
該接口只有一個方法:
```
public interface SessionStorageEvaluator {
public boolean isSessionStorageEnabled(Subject subject);
}
```
關于更詳細的API 說明,請參見 SessionStorageEvaluator 的[JavaDoc](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/mgt/SessionStorageEvaluator.html)。 你可以實現這一接口,并檢查 Subject,為了你可能做出這一決定的任何信息
##### Subject Inspection
但實現 isSessionStorageEnabled(subject)接口方法時,你可以一直查看 Subject 并訪問任何你需要用來作出決定的東西。
當然所有期望的 Subject 方法都是可用的(gePrincipals()等),但特定環境的 Subject 實例也是有價值的。
例如,在 Web 應用程序中,如果該決定必須基于當前 ServletRequest 中的數據,你可以獲取該 request 或該 response,因為運行時的Subjce 實例實際上就是一個 [WebSubject](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/web/subject/WebSubject.html) 實例:
```
...
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;
}
```
N.B.框架開發人員應該考慮到這種類型的訪問,并確保任何請求/調用/消息上下文對象可用是同過特定環境下的 Subject 實現的。聯系 Shiro 用戶郵件列表,如果你想幫助設置它,為了你的框架/環境。
#### Configuration 配置
在你實現了 SessionStorageEvaluator 接口后,你可以在 shiro.ini 中配置它:
```
[main]
...
sessionStorageEvaluator = com.mycompany.shiro.subject.mgt.MySessionStorageEvaluator
securityManager.subjectDAO.sessionStorageEvaluator = $sessionStorageEvaluator
...
```
### Web Applications
通常 Web 應用程序希望在每一個請求的基礎上容易地啟用或禁用會話的創建,不管是哪個 Subject 正在執行請求。這經常在支持 REST 及Messaging/RMI 構架上使用來產生很好的效果。例如,也許正常的終端用戶(使用瀏覽器的人)被允許創建和使用會話,但遠程的 API 客戶端使用REST 或 SOAP,不該擁有會話(因為它們在每一個請求上驗證, 常見于 REST/SOAP 體系結構)。
為了支持這種 hybrid/per-request (混合/每次請求)的能力,noSessionCreation 過濾器被添加到 Shiro 的默認“池”g過濾器中,為 Web 應用程序啟用的。該過濾器將會阻止在請求期間創建新的會話來保證無狀態的體驗。在shiro.ini 的\[urls\]項中,你通常定義該過濾器在所有其它過濾器之前來確保會話永遠不會被使用。
舉例:
```
[urls]
...
/rest/** = noSessionCreation, authcBasic, ...
```
這個過濾器允許現有會話的任何會話操作,但不允許在過濾的請求創建新的會話。也就是說,在請求或沒有會話存在的Subject 調用下面四個方法中的任何一個時,將會自動地觸發一個 DisabledSessionException 異常:
- httpServletRequest.getSession()
- httpServletRequest.getSession(true)
- subject.getSession()
- subject.getSession(true)
如果一個 Subject 在訪問 noSessionCreation-protected-URL(無會話創建保護的 URL) 之前已經有一個會話,則上述的四種調用仍然會如預期工作。
最后,在所有情況下,下面的調用將始終被允許:
- httpServletRequest.getSession(false)
- subject.getSession(false)
- Introduction
- 1. Introduction 介紹
- 2. Tutorial 教程
- 3. Architecture 架構
- 4. Configuration 配置
- 5. Authentication 認證
- 6. Authorization 授權
- 6.1. Permissions 權限
- 7. Realms
- 8. Session Management
- 9. Cryptography 密碼
- 10. Web
- 10.1. Configuration 配置
- 10.2. 基于路徑的 url 安全
- 10.3. Default Filters 默認過濾器
- 10.4. Session Management
- 10.5. JSP Tag Library
- 11. Caching 緩存
- 12. Concurrency & Multithreading 并發與多線程
- 13. Testing 測試
- 14. Custom Subjects 自定義 Subject
- 15. Spring Framework
- 16. Guice
- 17. CAS
- 18. Command Line Hasher
- 19. Terminology 術語
- 20. 10 Minute Tutorial 十分鐘教程
- 21. Beginner's Webapp Tutorial 初學者web應用教程
- 22. Application Security With Apache Shiro 用Shiro保護你的應用安全
- 23. CacheManager 緩存管理
- 24. Apache Shiro Cryptography Features 加密功能