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 |
# 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教育編程猿 的文章