Apache Shiro 會話與主體狀態的關係
有狀態的應用程式
預設情況下Shiro 的SecurityManager 例項會使用一個Subject 的Session儲存Subject 的身份ID(PrincipalCollection)和驗證狀態(subject.isAuthenticated())。這通常發生在一個Subject 登入後或當一個Subject的身份ID 通過Remember 服務後。
使用這種預設方式的好處是:
l 任何應用都可通過Session ID來關聯請求/呼叫/訊息,並且這是關聯使用者所必需的。例如,使用Subject.Builder來獲取相關的Subject
Serializable sessionId = //get from the inbound request or remote method invocation payload Subject requestSubject = new Subject.Builder().sessionId(sessionId).buildSubject(); |
l 任何"RememberMe"身份能夠在第一次訪問時就能持久化到會話的初始請求。這確保了Subject 被記住的身份可以跨請求儲存而不需要反序列化及將它解釋到每個請求。例如,在一個Web 應用程式中,沒有必要去讀取每一個請求的加密RememberMe Cookie,如果該身份在會話中是已知的。這可是一個很好的效能提升。
無狀態的應用程式
雖然上述的預設策略對於大多數應用程式而言是很好的(通常是可取的),但這對於無狀態的應用程式來說是不合適的。許多無狀態的架構規定在請求中不能存在持久狀態,這種情況下的Sessions
但這一要求帶來一個便利的代價——Subject 狀態不能跨請求保留。這意味著有這一要求的應用程式必須確保Subject狀態可以在每一個請求中以其他的方式代表。
這幾乎總是通過驗證每個由應用程式處理的請求/呼叫/訊息來完成的。例如,大多數無狀態Web 應用程式通常支援這一點通過執行HTTP 基本驗證,允許瀏覽器驗證每一個代表終端使用者的請求。遠端或訊息框架必須確保Subject 的身份和憑證連線到每一個呼叫或訊息的有效載荷,通常是由框架程式碼執行。
在無狀態應用中需要禁用將Subject狀態持久化到會話,可通過如下配置實現:
[main] … securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled = false |
需要注意的是,禁用使用Sessions 作為儲存策略的實現,但它沒有完全地禁用Sessions。如果你的任何程式碼顯式地呼叫subject.getSession()或subject.getSession(true),一個session 仍然會被建立。
混合方法
但,如果你想使用混合的方法呢?如果某些物件應該有會話而某些沒有?這種混合法方法能夠給許多應用程式帶來好處。例如:
l 也許human Subject(如Web 瀏覽器使用者)由於上面提供的好處能夠使用Session。
l 也許non-human Subject(如API 客戶端或第三方應用程式)不應該建立session 由於它們與軟體的互動可能會間歇或不穩定。
l 也許所有某種確定型別的Subject 或從某一確定位置訪問系統的應該將狀態保持在會話中,但所有其他的不應該。
如果你需要這個混合方法,你可以實現一個SessionStorageEvaluator介面,告訴Shiro 哪個Subject 支援會話儲存。
該介面只有一個方法:
public interface SessionStorageEvaluator { public boolean isSessionStorageEnabled(Subject subject); } |
例如,在Web 應用程式中,如果該決定必須基於當前ServletRequest 中的資料,你可以獲取該request 或該response,因為執行時的Subjce 例項實際上就是一個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; } |
在你實現了SessionStorageEvaluator 介面後,你可以在shiro.ini 中配置它:
[main] … sessionStorageEvaluator = com.mycompany.shiro.subject.mgt.MySessionStorageEvaluator securityManager.subjectDAO.sessionStorageEvaluator = $sessionStorageEvaluator ... |
WEB應用
通常Web 應用程式希望在每一個請求的基礎上容易地啟用或禁用會話的建立,不管是哪個Subject 正在執行請求。這經常在支援REST 及Messaging/RMI 構架上使用來產生很好的效果。例如,也許正常的終端使用者(使用瀏覽器的人)被允許建立和使用會話,但遠端的API 客戶端使用REST 或SOAP,不該擁有會話(因為它們在每一個請求上驗證,常見REST/SOAP 體系結構)。
為了支援這種hybrid/per-request 的能力,noSessionCreation 過濾器被新增到Shiro的預設為Web 應用程式啟用的“池”。該過濾器將會阻止在請求期間建立新的會話來保證無狀態的體驗。在shiro.ini 的[urls]項中,你通常定義該過濾器在所有其它過濾器之前來確保會話永遠不會被使用。
例如:
[urls] … /rest/** = noSessionCreation, authcBasic, ... |
這個過濾器允許現有會話的任何會話操作,但不允許在過濾的請求建立新的會話。也就是說,一個請求或沒有會話存在的Subject 呼叫下面四個方法中的任何一個時,將會自動地觸發一個DisabledSessionException 異常:
l httpServletRequest.getSession()
l httpServletRequest.getSession(true)
l subject.getSession()
l subject.getSession(true)
如果一個Subject 在訪問noSessionCreation-protected-URL 之前已經有一個會話,則上述的四種呼叫仍然會如預期般工作。
最後,在所有情況下,下面的呼叫將始終被允許:
l httpServletRequest.getSession(false)
l subject.getSession(false)