天天看點

shiro實戰系列(八)之安全管理器

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 配置:

shiro實戰系列(八)之安全管理器

但從頭開始建立一個 SessionManager 是一個複雜的任務且是大多數人不想親自做的情。Shiro 的立即可用的 SessionManager 實作是高度可定制的和可配置的,并滿足大多數的需要。本文檔的其餘部分假定你将使用 Shiro 的 預設 SessionManager 實作,當覆寫配置選項時。但請注意,你基本上可以建立或插入任何你想要的東西   。

五、Session Timeout

 預設地,Shiro 的 SessionManager 實作預設是 30 分鐘會話逾時。也就是說,如果任何 Session 建立後閑置(未被使 用,它的上次通路時間未被更新)的時間超過了 30 分鐘,那麼該 Session 就被認為是過期的,且不允許再被使用。

   你可以設定 SessionManager 預設實作的 globalSessionTimeout 屬性來為所有的會話定義預設的逾時時間。例如,如 果你想逾時時間是一個小時而不是 30 分鐘:  

shiro實戰系列(八)之安全管理器

六、  Per-Session Timeout

上面的 globalSessionTimeout 值預設是為建立的 Session 使用的。你可以在每一個會話的基礎上控制逾時時間通過設 置單獨的會話逾時時間值。與上面的 globalSessionTimeout 一樣,該值以毫秒(不是秒)為時間機關。

七、Session Listeners

Shiro 支援 SessionListener 概念來允許你對發生的重要會話作出反應。你可以實作 SessionListener 接口(或擴充易用 的 SessionListenerAdapter)并與相應的會話操作作出反應。   由于預設的 SessionManager sessionListeners 屬性是一個集合,你可以對 SessionManager 配置一個或多個 listener 實 現,就像其他在 shiro.ini 中的集合一樣:

shiro實戰系列(八)之安全管理器

八、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:

shiro實戰系列(八)之安全管理器

最後一行,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 上配置名字,例如:

shiro實戰系列(八)之安全管理器

隻要確定在 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 中:

shiro實戰系列(八)之安全管理器

十六、Session Validation & Scheduling

Sessions 必須被驗證,這樣任何無效(過期或停止)的會話能夠從會話資料存儲中删除。這保證了資料存儲不會由于不 能再次使用的會話而導緻寫入逾時。   由于性能上的原因,僅僅在 Sessions 被通路(也就是 subject.getSession())時驗證它們是否停止或過期。這意味着, 如果沒有額外的定期驗證,Session orphans(孤兒)将會開始填充會話資料存儲。

一個常見的說明孤兒的例子是 Web 浏覽器中的場景:比方說,使用者登入到 Web 應用程式并建立了一個會話來保留 資料(身份驗證狀态,購物車等)。如果使用者不登出,并在應用程式不知道的情況下關閉了浏覽器,則他們的會話 實質上是“躺在”會話資料存儲的(孤兒)。SessionManager 沒有辦法檢測使用者不再使用他們的浏覽器,同時該會 話永遠不會被再次通路(它是孤兒了)。   會話孤兒,如果它們沒有定期 清除,将會填充會話資料存儲(這是很糟糕的)。是以,為了防止丢放孤兒, SessionManager 實作支援 SessionValidationScheduler 的概念。SessionValidationScheduler 負責定期地驗證會話以確定 它們是否需要清理。  

十七、Default SessionValidationScheduler

預設可用的 SessionValidationScheduler 在所有環境中都是 ExecutorServiceSessionValidationScheduler,它使用 JDK ScheduledExecutorService 來控制驗證頻率。   預設地,該實作每小時執行一次驗證。你可以通過指定一個新的 ExecutorServiceSessionValidationScheduler 執行個體并指 定不同的間隔(以毫秒為機關)改變速率來更改驗證頻率:

shiro實戰系列(八)之安全管理器

十七、Custom SessionValidationScheduler

如果你希望提供一個自定義的 SessionValidationScheduler 實作,你可以指定它作為預設的 SessionManager 執行個體的一 個屬性。例如,在 shiro.ini 中:

shiro實戰系列(八)之安全管理器

十八、Disabling Session Validation

在某些情況下,你可能希望禁用會話驗證項,由于你建立了一個超出了 Shiro 控制的程序來為你執行驗證。例如, 也許你正在使用一個企業的 Cache 并依賴于緩存的 Time To Live 設定來自動地去除舊的會話。或者也許你已經制定 了一個計劃任務來自動清理一個自定義的資料存儲。在這些情況下你可以關掉 session validation scheduling:

shiro實戰系列(八)之安全管理器

當會話從會話資料存儲取回資料時它仍然會被驗證,但這會禁用掉 Shiro 的定期驗證。

十九、Enable Session Validation somewhere

如果你關閉了 Shiro 的 session validation scheduler,你必須通過其他的機制(計劃任務等)來執行定期的會話驗證。 這是保證會話孤兒不會填充資料存儲的唯一方法。  

二十、Invalid Session Deletion

正如我們上面所說的,進行定期的會話驗證主要目的是為了删除任何無效的(過期或停止)會話來確定它們不會填 充會話資料存儲。   預設地,某些應用程式可能不希望 Shiro 自動地删除會話。例如,如果一個應用程式已經提供了一個 SessionDAO 備 份資料存儲查詢,也許是應用程式團隊希望舊的或無效的會話在一定的時間内可用。這将允許團隊對資料存儲運作 查詢來判斷,例如,在上周某個使用者建立了多少個會話,或一個使用者會話的持續時間,或與之類似報告類型的查詢。  

在這些情形中,你可以關閉 invalid session deletion 項。例如,在 shiro.ini 中:

shiro實戰系列(八)之安全管理器

請注意!如果你關閉了它,你得為確定你的會話資料存儲不耗盡它的空間複雜。你必須自己從你的資料存儲中删除 無效的會話!   還要注意,即使你阻止了 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,如果該身份在會話中是已知的。這可是一個很好的性能提升。

任何"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實戰系列(八)之安全管理器

四、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 支援會話存儲。   該接口隻有一個方法:

shiro實戰系列(八)之安全管理器

關于更詳細的 API 說明,請參見 SessionStorageEvaluator 的 JavaDoc。   你可以實作這一接口,并檢查 Subject,為了你可能做出這一決定的任何資訊。

七、Subject Inspection

但實作 isSessionStorageEnabled(subject)接口方法時,你可以一直檢視 Subject 并通路任何你需要用來作出決定的東西。 當然所有期望的 Subject 方法都是可用的(getPrincipals()等),但特定環境的 Subject 執行個體也是有價值的。   例如,在 Web 應用程式中,如果該決定必須基于目前 ServletRequest 中的資料,你可以擷取該 request或該 response, 因為運作時的 Subjce 執行個體實際上就是一個 WebSubject 執行個體:

shiro實戰系列(八)之安全管理器
shiro實戰系列(八)之安全管理器

八、Web Applications

通常 Web 應用程式希望在每一個請求的基礎上容易地啟用或禁用會話的建立,不管是哪個 Subject 正在執行請求。 這經常在支援 REST 及 Messaging/RMI 構架上使用來産生很好的效果。例如,也許正常的終端使用者(使用浏覽器的人) 被允許建立和使用會話,但遠端的 API 用戶端使用 REST 或 SOAP,不該擁有會話(因為它們在每一個請求上驗證, 常見 REST/SOAP 體系結構)。   為了支援這種hybrid/per-request的能力,noSessionCreation過濾器被添加到Shiro的預設為Web應用程式啟用的“池”。 該過濾器将會阻止在請求期間建立新的會話來保證無狀态的體驗。在 shiro.ini 的[urls]項中,你通常定義該過濾器在 所有其它過濾器之前來確定會話永遠不會被使用。

shiro實戰系列(八)之安全管理器

這個過濾器允許現有會話的任何會話操作,但不允許在過濾的請求建立新的會話。也就是說,一個請求或沒有會話 存在的 Subject 調用下面四個方法中的任何一個時,将會自動地觸發一個 DisabledSessionException 異常:

httpServletRequest.getSession()

httpServletRequest.getSession(true)

subject.getSession()

subject.getSession(true)  

如果一個 Subject 在通路 noSessionCreation-protected-URL 之前已經有一個會話,則上述的四種調用仍然會如預期般 工作。   最後,在所有情況下,下面的調用将始終被允許:

httpServletRequest.getSession(false)

subject.getSession(false)