摘要
在前面的的入門學習中,已經基本瞭解Shiro會話管理基本內容,包括容器會話和本地會話,此篇,我們將深入詳細的學習Shiro安全框架的會話管理,內容比較多,我們將分上下兩個部分倆講解。上篇中包括使用會話和會話管理兩節,下篇包括會話集群和會話與主體狀態兩節。整體目錄如下:
篇章目錄
1、使用會話
2、會話管理
3、會話集群
4、會話與主體狀態
1.概述
Apache Shiro在安全框架領域提供了獨特的東西:從最簡單的命令行和智能手機應用程序到最大的企業Web應用集群的完整企業級會話解決方案。
這對許多應用程序有很大的影響——直到Shiro,如果您需要會話支持,則需要將應用程序部署到Web容器中或使用EJB Stateful Session Beans。 Shiro的會話支持比這兩種機制中的任何一種都更容易使用和管理的機制,並且它可以在任何應用程序中使用,無論是否有容器。
即使您將應用程序部署在Servlet或EJB容器中,仍然有令人信服的理由採用Shiro的會話支持而不是容器的會話管理。 以下是Shiro會話支持提供的最令人滿意的功能特性列表:
●1. 基於POJO/J2SE(IoC友好)——Shiro中的所有內容(包括會話和會話管理的所有方面)均基於接口並通過POJO實施。 這使您可以使用任何與JavaBeans兼容的配置格式(如JSON,YAML,Spring XML或類似機制)輕鬆配置所有會話組件。 您還可以輕鬆擴展Shiro的組件或根據需要編寫自己的組件,以完全自定義會話管理的功能。
●2. 輕鬆定製會話存儲——由於Shiro的會話對象基於POJO,因此會話數據可以輕鬆存儲到任意數量的數據源中。 這使您可以準確定製應用程序的會話數據駐留的位置,例如,文件系統、內存、網絡分佈式緩存,關係數據庫或專有數據存儲。
●3. 獨立於容器的集群!——使用Ehcache Terracotta,Coherence,GigaSpaces等易於使用的網絡緩存產品,可以輕鬆將Shiro的會話集群。 這意味著您只需一次性配置Shiro的會話群集,無論您部署哪個容器,會話都將以相同的方式被群集。不需要特定性容器配置!
●4. 異構客戶端訪問 ——與EJB或Web會話不同,Shiro會話可以通過各種客戶端技術"共享"。 例如,桌面應用程序可以在Web應用程序中"查看"和"共享"同一用戶使用的同一物理會話。 除了Shiro以外,我們還沒有注意到可以支持這個功能的其它框架。
●5. 事件監聽器 ——事件監聽器允許您在會話生命週期中監聽生命週期事件。 您可以監聽這些事件並對其進行響應以獲取自定義應用程序行為。例如,在會話過期時更新用戶記錄。
●6. 主機地址保留 ——Shiro會話保留髮起會話的主機的IP地址或主機名。 這使您可以確定用戶所在的位置並做出相應的反應(通常在IP關聯是確定的Intranet環境中很有用)。
●7. 閒置/過期支持 - 會話由於處於非活動狀態而如期終止,但可以通過touch()方法延長會話,以便在需要時保持活動狀態。 在富Internet應用程序(RIA)環境中,此用戶可能正在使用桌面應用程序,但可能不定期與服務器通信,但服務器會話不應過期。
●8. 透明的Web使用 ——Shiro的Web支持完全實現並支持Sessions的Servlet 2.5規範(HttpSession接口及其所有相關的API)。 這意味著您可以在現有的Web應用程序中使用Shiro會話,並且不需要更改任何現有的Web代碼。
●9. 可用於SSO ——由於Shiro會話是基於POJO的,因此它們可以輕鬆存儲到任何數據源中,並且可以在需要時跨應用程序"共享"。 我們稱之為'窮人的SSO',它可以用來提供簡單的登錄體驗,因為共享會話可以保留身份驗證狀態。
以上所列的Shiro會話管理相關特性,基本上可以滿足任何情況下的需要。
1. 使用會話
幾乎像Shiro中的其他事物一樣,您通過與當前正在使用執行的主體進行交互而來獲得一個會話對象(Session):
Subject currentUser = SecurityUtils.getSubject(); Session session = currentUser.getSession(); session.setAttribute( "someKey", someValue); |
currentUser.getSession() 方法是調用currentUser.getSession(true)的快捷方式。
對於那些熟悉HttpServletRequest API的人來說,Subject.getSession(boolean create)方法的功能與HttpServletRequest.getSession(boolean create)方法的功能相同:
●如果Subject已有Session,則布爾參數將被忽略,會話Session將立即被返回;
●如果Subject還沒有會話,並且create 參數為true,則會創建並返回新的會話Session。
●如果Subject尚未具有會話並且create 參數為false,則不會創建新會話並返回null。
注意:getSession調用可以在任何應用程序中工作,即使是非web應用程序也一樣。
在開發框架代碼時可以使用subject.getSession(false)以獲得良好效果,以確保不會創建不必要Session。
一旦你獲得了一個主體的會話(Session),你可以用它做很多事情,比如設置或檢索屬性,設置其超時等等。 查看Session JavaDoc(http://shiro.apache.org/static/current/apidocs/org/apache/shiro/session/Session.html)以查看單個會話的可能性(Session的API參考)。
2. 會話管理器(SessionManager)
SessionManager,就像它的名字可能暗示的那樣,為應用程序中的所有主體(創建,刪除,閒置和驗證等)管理會話。與Shiro中的其他核心體系結構組件一樣,SessionManager是由SecurityManager維護的頂級組件。
默認的SecurityManager實現默認使用開箱即用的DefaultSessionManager。 DefaultSessionManager實現提供了應用程序所需的所有企業級會話管理功能,如會話驗證,孤立清理等。這可以在任何應用程序中使用。
提示:Web應用程序根據需要可以使用不同的SessionManager實現。 具體請參閱Web文檔以瞭解特定Web的會話管理信息(可以參考前面的文章“Web應用支持”部分描述)。
與SecurityManager管理的所有其他組件一樣,SessionManager可以通過Shiro的所有SecurityManager默認實現(基於getSessionManager()/setSessionManager()方法)上的JavaBeans風格的getter/setter方法獲取或設置。 或者,如果使用shiro.ini配置實現:
[main] ... sessionManager = com.foo.my.SessionManagerImplementation securityManager.sessionManager = $sessionManager |
(在shiro.ini中配置新的會話管理)
但是從頭去創建一個SessionManager是一項複雜的任務,而不是大多數人想要做的事情。 Shiro的開箱即用的SessionManager實現高度可定製且可配置,並且將滿足大多數需求。 本文檔的其餘大部分內容都假設您在使用Shiro的默認SessionManager實現所覆蓋的配置選項,但請注意,您幾乎可以根據自己的意願創建或插入任何內容。
3.1 會話超時
默認情況下,Shiro的SessionManager實現的超時時間默認為30分鐘。 也就是說,如果創建的會話保持空閒狀態(未使用,其lastAccessedTime未更新)持續30分鐘或更長時間,則會話被視為已過期,並且不會再被使用。
您可以設置默認SessionManager實現的globalSessionTimeout屬性來定義所有會話的默認超時值。 例如,如果您希望超時時間為一小時而不是30分鐘,可如下操作:
[main] ... # 3,600,000 milliseconds = 1 hour securityManager.sessionManager.globalSessionTimeout = 3600000 |
(在配置文件Shiro.ini中配置默認超時時間)
每會話超時
上述globalSessionTimeout值是所有新創建的會話的默認值。 您可以通過設置單個會話的超時值來控制每個會話的會話超時。 像上面的globalSessionTimeout一樣,該值是以毫秒(而不是秒)為單位的時間。
比如針對一個敏感用戶的會話時間,你可以直接調用Session的API設置其時間值。
3.2 會話監聽器
Shiro支持SessionListener的概念,允許您在發生重要會話事件時作出反應。 您可以實現SessionListener接口(或擴展便利的SessionListenerAdapter)並相應地對會話操作進行響應。
由於默認的SessionManager的sessionListeners屬性是一個集合,因此可以為SessionManager配置一個或多個監聽器實現,像在shiro.ini中的任何其他集合來配置一樣。
[main] ... aSessionListener = com.foo.my.SessionListener anotherSessionListener = com.foo.my.OtherSessionListener securityManager.sessionManager.sessionListeners = $aSessionListener, $anotherSessionListener, etc. |
(配置文件中的監聽器配置)
注意:SessionListeners會在任何會話(Session)中發生事件時被通知 ,而不僅僅是特定會話。
3.3 會話存儲
每當創建或更新會話時,其數據都需要保存到存儲設備中,以便以後可以由應用程序訪問。 同樣,如果會話無效且用時過長,則需要將其從存儲中刪除,以便會話數據存儲空間不會耗盡。 SessionManager實現將這些創建/讀取/更新/刪除(CRUD)操作委託給內部組件SessionDAO,該組件反映了數據訪問對象(DAO)設計模式。
SessionDAO的強大之處在於您可以實現此接口與任何您希望的數據存儲進行通信。 這意味著您的會話數據可以駐留在內存中、文件系統中、關係數據庫或NoSQL數據存儲中或您需要的任何其他位置。 您可以控制持久化行為。
您可以將任何SessionDAO實現配置為缺省SessionManager實例上的屬性值。 例如,在shiro.ini中配置:
[main] ... sessionDAO = com.foo.my.SessionDAO securityManager.sessionManager.sessionDAO = $sessionDAO |
但是,正如你所期望的那樣,Shiro已經有了一些很好的SessionDAO實現,你可以開箱即用或根據自己的需要子類化後再用。
Web應用注意:
上述securityManager.sessionManager.sessionDAO = $ sessionDAO配置,僅適用於使用Shiro本機會話管理器的情況。 Web應用程序默認不使用本機會話管理器,而是保留Servlet容器的默認會話管理器,該會話管理器不支持SessionDAO。 如果您希望在基於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 |
配置SessionDAO須知:
Shiro的默認配置的本地化SessionManagers只使用內存會話存儲。 這不適用於大多數生產應用。 大多數生產應用程序都希望配置提供EHCache支持(見下文)或提供自己的SessionDAO實現。 請注意,Web應用程序默認使用基於servlet容器的SessionManager,並不存在此問題。 這只是使用Shiro本地SessionManager時的一個問題。
3.3.1 EHCache SessionDAO
EHCache默認情況下未啟用,但如果您不打算實施自己的SessionDAO,強烈建議您為Shiro的SessionManagement啟用EHCache支持。 EHCache SessionDAO會將會話存儲在內存中,並在內存受限時支持溢出到磁盤。 這對生產應用程序非常有用,可確保您在運行時不會隨機"丟失"會話。
推薦-1:使用EHCache作為默認值
推薦-2:容器獨立的會話集群
如果您需要快速實現獨立於容器的會話群集,EHCache也是一個不錯的選擇。 您可以在EHCache背後透明地插入TerraCotta(http://www.terracotta.org/),並擁有獨立於容器的群集會話緩存。 再也不用擔心Tomcat,JBoss,Jetty,WebSphere或WebLogic特定的會話集群了!
為會話啟用EHCache非常簡單。 首先,確保您的類路徑中包含shiro-ehcache-
一旦進入類路徑,這第一個shiro.ini示例將向您展示如何將EHCache用於Shiro的所有緩存需求(而不僅僅是會話支持):
[main] sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO securityManager.sessionManager.sessionDAO = $sessionDAO cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager securityManager.cacheManager = $cacheManager |
(針對所有Shiro緩存需求的EhCache配置)
最後一行securityManager.cacheManager = $ cacheManager為所有Shiro需求配置一個CacheManager。 該CacheManager實例將自動傳播到SessionDAO(實現CacheManagerAware接口的EnterpriseCacheSessionDAO)。
然後,當SessionManager要求EnterpriseCacheSessionDAO持久化Session時,它將使用EHCache支持的Cache實現來持久化會話數據。
Web應用注意:
不要忘記,在使用Shiro本地化SessionManager實現時,分配SessionDAO是其一項特性。 Web應用默認使用不支持SessionDAO的、基於Servlet容器的SessionManager。 如果您想在Web應用程序中使用基於Ehcache的會話存儲,請按上文所述配置本機Web SessionManager。
1)EHCache會話緩存配置
默認情況下,EhCacheManager使用Shiro特定ehcache.xml文件來設置會話緩存區域和其它必要的配置,以確保會話正確存儲和檢索。
但是,如果您希望更改緩存設置,或配置您自己的ehcache.xml或EHCache net.sf.ehcache.CacheManager實例,則需要配置緩存區域以確保會話正確地處理。
如果您查看默認的ehcache.xml文件,您將看到以下shiro-activeSessionCache緩存配置:
maxElementsInMemory="10000" overflowToDisk="true" eternal="true" timeToLiveSeconds="0" timeToIdleSeconds="0" diskPersistent="true" diskExpiryThreadIntervalSeconds="600"/> |
如果您希望使用自己的ehcache.xml文件,請確保您為Shiro的需求定義了類似的緩存條目。 很可能您會更改maxElementsInMemory屬性值以滿足您的需求。 但是,在您自己的配置中至少存在以下兩個屬性(並且未更改)是非常重要的:
● overflowToDisk ="true"——這確保瞭如果用完進程內存,會話將不會丟失,並且可以序列化到磁盤
● eternal ="true" ——確保緩存條目(會話實例)永不過期或由緩存自動清除。 這是必要的,因為Shiro根據預定的流程進行自己的驗證(請參閱下面的"會話驗證&調度")。 如果我們將其關閉,那麼緩存可能會驅逐Sessions,而Shiro不知道它會導致問題。
2)EHCache會話緩存名稱
默認情況下,EnterpriseCacheSessionDAO向CacheManager請求一個名為"shiro-activeSessionCache"的緩存。 如上所述,此緩存名稱/區域將在ehcache.xml中進行配置。
如果您想使用其他名稱而不是此默認名稱,則可以在EnterpriseCacheSessionDAO上配置該名稱,如下所示:
... sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO sessionDAO.activeSessionsCacheName = myname ... |
(在shiro.ini中為Shiro的活動會話緩存配置緩存名稱)
只需確保ehcache.xml中的相應條目與該名稱匹配,並且已經配置了overflowToDisk ="true"和eternal ="true",如上所述。
3.3.2 定製會話ID
Shiro的SessionDAO實現使用內部SessionIdGenerator組件在每次創建新會話時生成新的會話ID。 生成ID,分配給新創建的Session實例,然後Session通過SessionDAO被保存。
默認的SessionIdGenerator是一個JavaUuidSessionIdGenerator,它根據Java UUID生成字符串 ID。 該實現適用於所有生產環境。
如果這不符合您的需求,您可以實現SessionIdGenerator接口並在Shiro的SessionDAO實例上配置實現。 在shiro.ini中配置示例如下:
[main] ... sessionIdGenerator = com.my.session.SessionIdGenerator securityManager.sessionManager.sessionDAO.sessionIdGenerator = $sessionIdGenerator |
(配置自定義會話ID生成器)
3.4 會話驗證&調度
會話必須經過驗證,以便可以從會話數據存儲中刪除任何無效(過期或停止)會話。 這可以確保隨著時間的推移,數據存儲不會被再也不使用的會話佔用。
出於性能方面的原因,只有驗證Sessions才能瞭解是否在訪問它們時被停止或過期(即subject.getSession())。 這意味著如果沒有額外的常規定期驗證,會話“孤兒”(或說孤立會話)會漸漸填滿會話數據存儲域。
一個常見的描述“孤立”示例的是網絡瀏覽器場景:假設用戶登錄到Web應用程序,並創建會話以保留數據(身份驗證狀態,購物車等)。 如果用戶在應用程序不知道的情況下沒有註銷並關閉瀏覽器,他們的會話基本上只是在會話數據存儲中"閒置"(孤立)。 SessionManager無法檢測到用戶不再使用瀏覽器,並且會話永遠不會再被訪問(它是孤立的)。
會話孤兒,如果他們不經常清除,會填滿會話數據存儲空間(這會很糟糕)。 因此,為了防止孤兒堆積,SessionManager實現支持SessionValidationScheduler的概念。 SessionValidationScheduler負責定期驗證會話,以確保在必要時對其進行清理。
3.4.1 缺省會話驗證調度器(SessionValidationScheduler)
可在所有環境中使用的默認SessionValidationScheduler,它是使用JDK的ScheduledExecutorService控制驗證發生頻率(多久驗證出現)的ExecutorServiceSessionValidationScheduler。
默認情況下,此實現將每小時執行一次驗證。 您可以通過指定ExecutorServiceSessionValidationScheduler的新實例並指定不同的時間間隔(以毫秒為單位)來更改驗證發生的速率,示例如下:
[main] ... sessionValidationScheduler = org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler # Default is 3,600,000 millis = 1 hour: sessionValidationScheduler.interval = 3600000 securityManager.sessionManager.sessionValidationScheduler = $sessionValidationScheduler |
(shiro.ini中的ExecutorServiceSessionValidationScheduler時間間隔配置)
3.4.2 自定義SessionValidationScheduler
如果您希望提供自定義SessionValidationScheduler實現,則可以將其指定為默認SessionManager實例的屬性。 在shiro.ini中配置參考示例如下:
[main] ... sessionValidationScheduler = com.foo.my.SessionValidationScheduler securityManager.sessionManager.sessionValidationScheduler = $sessionValidationScheduler |
(在shiro.ini中配置一個自定義的SessionValidationScheduler)
3.4.3 禁用會話驗證
在某些情況下,您可能希望完全禁用會話驗證,因為您已經在Shiro的控制之外設置了一個過程來為您執行驗證。 例如,也許您正在使用企業緩存,並依靠緩存的Time To Live設置來自動清除舊會話。 或者,您可能已經設置了一個cron作業來自動清除自定義數據存儲。 在這些情況下,您可以關閉會話驗證調度:
[main] ... securityManager.sessionManager.sessionValidationSchedulerEnabled = false |
(在shiro.ini中禁用會話驗證調度)
會話在從會話數據存儲中檢索時仍然會被驗證,但是這會禁用Shiro的定期驗證。
注意:在某處啟用會話驗證時
如果關閉Shiro的會話驗證調度器,則必須通過其他一些機制(cron作業等)執行定期會話驗證。 這是保證會話“孤兒”不會填滿數據存儲的唯一方法。
3.4.4 失效會話刪除
如上所述,定期會話驗證的目的主要是刪除任何無效(過期或停止)會話,以確保它們不會填滿會話數據存儲。
默認情況下,每當Shiro檢測到無效會話時,它都會嘗試通過SessionDAO.delete(session)方法將其從基礎會話數據存儲中刪除。 對於大多數應用程序來說,這是確保會話數據存儲空間不被耗盡的良好做法。
但是,有些應用程序可能不希望Shiro自動刪除會話。 例如,如果應用程序提供了支持可查詢數據存儲的SessionDAO,則應用程序團隊可能希望舊的或無效的會話在一段時間內可用。 這將允許團隊針對數據存儲運行查詢,以查看例如用戶上週創建了多少會話,或用戶會話的平均持續時間或類似的報告類型查詢。
在這些情況下,您可以完全關閉無效會話刪除配置。 在shiro.ini中關閉刪除的示例如下:
[main] ... securityManager.sessionManager.deleteInvalidSessions = false |
不過要小心! 如果您關閉此功能,則有責任確保會話數據存儲不會耗盡其空間。 您必須自己從您的數據存儲區中刪除無效會話!
還要注意的是,即使您阻止Shiro刪除無效會話,您仍應該以某種方式啟用會話驗證 - 無論是通過Shiro的現有驗證機制還是通過您自己提供的自定義機制(請參閱上面的"禁用會話驗證"一節以獲取更多信息)。 驗證機制會更新您的會話記錄以反映無效狀態(例如,當它失效,上次訪問時等),即使您將在其他時間手動刪除它們。
警告——
● 如果您將Shiro配置為不刪除無效會話,則您有責任確保會話數據存儲不會耗盡其空間。 您必須自己從您的數據存儲區中刪除無效會話!
● 另請注意,禁用會話刪除與禁用會話驗證調度不同。 您幾乎應該總是使用會話驗證調度機制- 無論是直接支持的還是您自己的。
Ok,關於會話管理的上篇就到寫這裡,請繼續關注下篇:會話集群及會話主體狀態。
閱讀更多 3T教育編程猿 的文章