1. 程式人生 > >SpringBoot系列: SpringBoot Web專案中使用Shiro 之二

SpringBoot系列: SpringBoot Web專案中使用Shiro 之二

==================================
Shiro 的加深理解:
==================================
1. Shiro 和 Spring 系元件的對標, Shiro = Spring Security + Spring Session. 就是說 Shiro 不僅僅是一個安全框架, 同時也是一個 Session 管理框架. 其實這也是很自然的事情, Shiro 會攔截所有的 web 請求, 通過 SecurityManager 進行安全稽核, 接著 SecurityManager 又委託 SessionManager 進行 session 管理. 當然 Shiro 並沒有強制我們一定要使用它來進行 Session 管理.
2. Shiro Session 管理: 就是和普通的 web session 管理一樣, 管理 session 屬性, 預設是儲存在記憶體中, 也可以儲存在分散式的儲存中, 比如 redis.
3. Shiro cache 的內容是: 僅僅快取 Realm 的認證和鑑權相關的資料, 不會包含 Session 屬性資料.

預設情況下, Web 伺服器端的 Session 都是儲存在記憶體中, 如果服務重啟升級, Session 就丟了, 如果是叢集部署, session 儲存在單機記憶體中也不太合適, 這時就需要引入集中 session 儲存了, 現在比較流行使用 redis 儲存.

 

==================================
Shiro 內建的 Session Manager
==================================
Session Manager, Shiro 使用 session 來管理 subject 的生命週期, 包括: 建立/停止/過期 等等, shiro 內建了三個實現:
1. DefaultSessionManager
    DefaultSecurityManager 使用的預設 Session Manager, 用於 JavaSE 環境.
2. ServletContainerSessionManager
    DefaultWebSecurityManager 預設使用的 Session Manager, 使用 Servlet 容器伺服器的會話. 如果執行在 Tomcat 中, HttpSession 介面的實現類是 org.apache.catalina.session.StandardSessionFacade. 用於 Web 環境.
3. DefaultWebSessionManager
    可以替代 ServletContainerSessionManager, 自定義 Session 的管理策略, 甚至包括自定義持久化方案, Shiro 提供了一個預設的 MemorySessionDAO 持久化 DAO 實現, 也有 redis 持久化方案, 比如這個開源專案, http://alexxiyang.github.io/shiro-redis/

 


下面是啟用 DefaultWebSessionManager 的簡單示例, 採用了預設的 MemorySessionDAO 來持久化 (應該是基於併發 HashMap 類).

@Bean
public DefaultWebSessionManager sessionManager() {
    DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
    sessionManager.setGlobalSessionTimeout(1800000l);
    sessionManager.setDeleteInvalidSessions(
true); sessionManager.setSessionValidationSchedulerEnabled(true); sessionManager.setSessionIdCookieEnabled(true); SimpleCookie cookie = new SimpleCookie(ShiroHttpSession.DEFAULT_SESSION_ID_NAME); //cookie 名為: JSESSIONID cookie.setHttpOnly(true); cookie.setMaxAge(1800000); sessionManager.setSessionIdCookie(cookie); return sessionManager; }

==================================
使用 Shiro 框架後幾個常用物件的具體實現類
==================================
Shiro 不管是採用 ServletContainerSessionManager 還是 DefaultWebSessionManager, 使用 Shiro 框架後, 檢視方法注入的 HttpServletRequest 引數都會被替換為 shiro 的 ShiroHttpServletRequest 實現類.

另外需要注意, Shiro subject.getSession() 返回的 session 物件, 實現的介面是 org.apache.shiro.session.Session, 這個介面和標準 HttpSession 介面沒有父子關係, 不能簡單強轉, Shiro 內部有相互轉換的程式碼.
1. subject.getSession() 返回值的具體類: org.apache.shiro.subject.support.DelegatingSubject$StoppingAwareProxiedSession
2. Controller 檢視方法注入的 HttpSession 介面引數, 具體實現類是 org.apache.catalina.session.StandardSessionFacade
3. Controller 檢視方法中, 通過 request.getSession() 方法, 返回值的型別和 檢視方法注入的 HttpSession 引數型別一致.

詳情物件的型別如下:

subject.getSession()     : org.apache.shiro.subject.support.DelegatingSubject$StoppingAwareProxiedSession
httpSession              : org.apache.catalina.session.StandardSessionFacade
httpRequest.getSession() : org.apache.catalina.session.StandardSessionFacade
httpRequest              : org.apache.shiro.web.servlet.ShiroHttpServletRequest
httpResponse             : org.apache.catalina.connector.ResponseFacade

 

下圖是幾個Session類之間的關係:

 

==================================
Shiro Session 和標準 HttpSession 的互操作性
==================================
雖然 org.apache.shiro.session.Session 介面和標準的 javax.servlet.http.HttpSession 介面沒有父子關係, 它們不能簡單強轉, 但是對於 Session 屬性管理是相通的, 經我測試, Shiro 不管是採用 ServletContainerSessionManager 作為 session 管理器, 還是採用 DefaultWebSessionManager, 兩套 session 物件的屬性讀寫是相通的.

這為我們提供了極大的便利, 也就是說我們既可以使用標準的 HttpSession 物件來讀寫 session 屬性, 也可以使用 Shiro 的 Session 物件來讀寫.

 

// HttpSession 主動寫 Session 屬性示例
@RequestMapping("/springWrite")
@ResponseBody
public String springWrite(HttpServletRequest httpRequest, HttpSession httpSession){
    httpSession.setAttribute("Spring_Attr", "Spring_Attr_Value");
    System.out.println(httpSession.getAttribute("Spring_Attr"));

    System.out.println("=======");
    Session session = SecurityUtils.getSubject()
            .getSession();
    System.out.println(session.getAttribute("Spring_Attr"));
    return "springWrite";
}

// Shiro Session 主動寫 Session 屬性示例
@RequestMapping("/shiroWrite")
@ResponseBody
public String shiroWrite(HttpServletRequest httpRequest, HttpSession httpSession){
    Session session = SecurityUtils.getSubject()
            .getSession();
    session.setAttribute("Shiro_Attr", "Shiro_Attr_value");
    System.out.println(session.getAttribute("Shiro_Attr"));

    System.out.println("=======");
    System.out.println(httpSession.getAttribute("Shiro_Attr"));
    return "shiroWrite";
}

 

下面是 shiro session 支援的方法:

    Session session = subject.getSession(); 
    session.getId()
    session.getTimeout())
    session.getStartTimestamp();  
    session.getLastAccessTime();
    session.setAttribute("key", "123"); 
    session.removeAttribute("key");  
    session.touch();  
    session.stop();  

在 J2SE 專案中, 使用 Subject.logout() 會自動呼叫 Session.stop() 方法來銷燬會話, 如果在 web 中, 呼叫 javax.servlet.http.HttpSession.invalidate() 也會自動呼叫 Session.stop() 方法進行銷燬 Shiro 的會話.  

 

==================================
到底要不要讓 Shiro 全面接管 Session 管理?
==================================
前面已經提到: Shiro = Spring Security + Spring Session, 在安全管理這塊, 我覺得 Shiro 比 Spring Security 強太多了, Spring Security 搞的太複雜了. 現在的問題是, 要不要讓 Shiro 全面接管 Session 管理?

推薦方案是 ServletContainerSessionManager + Spring Session, 也就是說 Session 管理還是交給 Spring Session, Session 屬性操作還是使用標準的HttpSession注入物件來完成,  不太推薦使用 DefaultWebSessionManager 使用持久化 Session 資訊.

說明如下:
1. 推薦 ServletContainerSessionManager 而不是 DefaultWebSessionManager, 這樣就能更好利用 Spring Session 生態. 如果使用 DefaultWebSessionManager, 開箱即用的 SessionDAO 只有 MemorySessionDAO 和 開源的 shiro-redis (地址 http://alexxiyang.github.io/) , 生態要比 Spring Session 差.
比如, 我們我們
2. 有剋制地使用 SecurityUtils.getSubject().getSession(), 雖然 Shiro Session 和標準 HttpSession 具有很好的互操作性, 但在檢視方法中, 還是推薦使用 HttpSession 引數注入物件, 在 Shiro realm 實現類中, 如果要讀寫 session, 直接用 SecurityUtils.getSubject().getSession() 非常方便.



==================================
參考
==================================
使用 redis 進行基於 shiro 的 session 叢集共享
http://www.cnblogs.com/sunshine-2015/p/5686750.html
shiro 實現 APP、web 統一登入認證和許可權管理
http://www.cnblogs.com/sunshine-2015/p/5515429.html