天天看點

shiro源碼篇 - shiro的session共享,你值得擁有

  老師對小明說:"乳就是小的意思,比如乳豬就是小豬,乳名就是小名,請你用乳字造個句"

  小明:"我家很窮,隻能住在40平米的乳房"

  老師:"..., 這個不行,換一個"

  小明:"我每天上學都要跳過我家門口的一條乳溝"

  老師:"......, 這個也不行,再換一個"

  小明:"老師,我想不出來了,把我的乳頭都想破了!"

  shiro的session建立與session的查詢、更新、過期、删除中,shiro對session的操作基本都講到了,但還缺一個session共享沒有講解;session共享的原理其實在自定義session管理一文已經講過了,本文不講原理,隻看看shiro的session共享的實作。

    如果是單機應用,那麼談不上session共享,session放哪都無所謂,不在乎放到預設的servlet容器中,還是抽出來放到單獨的地方;

    也就是說session共享是針對叢集(或分布式、或分布式叢集)的;如果不做session共享,仍然采用預設的方式(session存放到預設的servlet容器),當我們的應用是以叢集的方式釋出的時候,同個使用者的請求會被分發到不同的叢集節點(分發依賴具體的負載均衡規則),那麼每個處理同個使用者請求的節點都會重新生成該使用者的session,這些session之間是毫無關聯的。那麼同個使用者的請求會被當成多個不同使用者的請求,這肯定是不行的。

    實作方式其實有很多,甚至可以不做session共享,具體有哪些,大家自行去查資料。本文提供一種方式:redis實作session共享,就是将session從servlet容器抽出來,放到redis中存儲,所有叢集節點都從redis中對session進行操作。

  SessionDAO其實是用于session持久化的,但裡面有緩存部分,具體細節我們往下看

  shiro已有SessionDAO的實作如下

  SessionDAO接口提供的方法如下

shiro源碼篇 - shiro的session共享,你值得擁有
shiro源碼篇 - shiro的session共享,你值得擁有

View Code

    SessionDAO給出了從持久層(一般而言是關系型資料庫)操作session的标準。

  AbstractSessionDAO提供了SessionDAO的基本實作,如下

shiro源碼篇 - shiro的session共享,你值得擁有
shiro源碼篇 - shiro的session共享,你值得擁有

    SessionDao的基本實作,實作了SessionDao的create、readSession(具體還是依賴AbstractSessionDAO子類的doCreate、doReadSession實作);同時加入了自己的sessionId生成器,負責sessionId的操作。

  CachingSessionDAO提供了session緩存的功能,如下

shiro源碼篇 - shiro的session共享,你值得擁有
shiro源碼篇 - shiro的session共享,你值得擁有

    是應用層與持久化層之間的緩存層,不用頻繁請求持久化層以提升效率。重寫了AbstractSessionDAO中的create、readSession方法,實作了SessionDAO中的update、delete、getActiveSessions方法,預留doUpdate和doDelele給子類去實作(doXXX方法操作的是持久層)

  MemorySessionDAO,SessionDAO的簡單記憶體實作,如下

shiro源碼篇 - shiro的session共享,你值得擁有
shiro源碼篇 - shiro的session共享,你值得擁有

    将session儲存在記憶體中,存儲結構是ConcurrentHashMap;項目中基本不用,即使我們不實作自己的SessionDAO,一般用的也是EnterpriseCacheSessionDAO。

  EnterpriseCacheSessionDAO,提供了緩存功能的session維護,如下

shiro源碼篇 - shiro的session共享,你值得擁有
shiro源碼篇 - shiro的session共享,你值得擁有

    設定了預設的緩存管理器(AbstractCacheManager)和預設的緩存執行個體(MapCache),實作了緩存效果。從父類繼承的持久化操作方法(doXXX)都是空實作,也就說EnterpriseCacheSessionDAO是沒有實作持久化操作的,僅僅隻是簡單的提供了緩存實作。當然我們可以繼承EnterpriseCacheSessionDAO,重寫doXXX方法來實作持久化操作。

  總結下:SessionDAO定義了從持久層操作session的标準;AbstractSessionDAO提供了SessionDAO的基礎實作,如生成會話ID等;CachingSessionDAO提供了對開發者透明的session緩存的功能,隻需要設定相應的 CacheManager 即可;MemorySessionDAO直接在記憶體中進行session維護;而EnterpriseCacheSessionDAO提供了緩存功能的session維護,預設情況下使用 MapCache 實作,内部使用ConcurrentHashMap儲存緩存的會話。因為shiro不知道我們需要将session持久化到哪裡(關系型資料庫,還是檔案系統),是以隻提供了MemorySessionDAO持久化到記憶體(聽起來怪怪的,記憶體中能說成持久層嗎)

    shiro的session共享其實是比較簡單的,重寫CacheManager,将其操作指向我們的redis,然後實作我們自己的CachingSessionDAO定制緩存操作和緩存持久化。

    自定義CacheManager

      ShiroRedisCacheManager

shiro源碼篇 - shiro的session共享,你值得擁有
shiro源碼篇 - shiro的session共享,你值得擁有

      ShiroRedisCache

shiro源碼篇 - shiro的session共享,你值得擁有
shiro源碼篇 - shiro的session共享,你值得擁有

    自定義CachingSessionDAO

      繼承EnterpriseCacheSessionDAO,然後重新設定其CacheManager(替換掉預設的記憶體緩存器),這樣也可以實作我們的自定義CachingSessionDAO,但是這是優選嗎;如若我們實作持久化,繼承EnterpriseCacheSessionDAO是優選,但如果隻是實作session緩存,那麼CachingSessionDAO是優選,自定義更靈活。那麼我們還是繼承CachingSessionDAO來實作我們的自定義CachingSessionDAO

      ShiroSessionDAO

shiro源碼篇 - shiro的session共享,你值得擁有
shiro源碼篇 - shiro的session共享,你值得擁有

    最後将ShiroSessionDAO執行個體指派給SessionManager執行個體,再講SessionManager執行個體指派給SecurityManager執行個體即可

    具體代碼請參考spring-boot-shiro

    底層還是利用Filter + HttpServletRequestWrapper将對session的操作接入到自己的實作中來,而不走預設的servlet容器,這樣對session的操作完全由我們自己掌握。

    shiro的session建立中其實講到了shiro中對session操作的基本流程,這裡不再贅述,沒看的朋友可以先去看看再回過頭來看這篇。本文隻講shiro中,如何将一個請求的session接入到自己的實作中來的;shiro中有很多預設的filter,我會單獨開一篇來講shiro的filter,這篇我們先不糾結這些filter。

    OncePerRequestFilter中doFilter方法如下

shiro源碼篇 - shiro的session共享,你值得擁有
shiro源碼篇 - shiro的session共享,你值得擁有

    上圖中,我可以看到AbstractShiroFilter的doFilterInternal放中将request封裝成了shiro自定義的ShiroHttpServletRequest,将response也封裝成了shiro自定義的ShiroHttpServletResponse。既然Filter中将request封裝了ShiroHttpServletRequest,那麼到我們應用的request就是ShiroHttpServletRequest類型,也就是說我們對session的操作最終都是由shiro完成的,而不是預設的servlet容器。

    另外補充一點,shiro的session建立不是懶建立的。servlet容器中的session建立是第一次請求session(第一調用request.getSession())時才建立。shiro的session建立如下圖

    此時,還沒登入,但是subject、session已經建立了,隻是subject的認證狀态為false,說明還沒進行登入認證的。至于session建立過程已經儲存到redis的流程需要大家自行去跟,或者閱讀我之前的博文

  1、當以叢集方式對外提供服務的時候,不做session共享也是可以的

    可以通過ip_hash的機制将同個ip的請求定向到同一台後端,這樣保證使用者的請求始終是同一台服務處理,與單機應用基本一緻了;但這有很多方面的缺陷(具體就不詳說了),不推薦使用。

  2、servlet容器之間做session同步也是可以實作session共享的

    一個servlet容器生成session,其他節點的servlet容器從此servlet容器進行session同步,以達到session資訊一緻。這個也不推薦,某個時間點會有session不一緻的問題,畢竟同步過程受到各方面的影響,不能保證session實時一緻。

  3、session共享實作的原理其實都是一樣的,都是filter + HttpServletRequestWrapper,隻是實作細節會有所差別;有興趣的可以看下spring-session的實作細節。

  4、如果我們采用的spring內建shiro,其實可以将緩存管理器交由spring管理,相當于由spring統一管理緩存。

  5、shiro的CacheManager不隻是管理session緩存,還管理着身份認證緩存、授權緩存,shiro的緩存都是CacheManager管理。但是身份認證緩存預設是關閉的,個人也不推薦開啟。

  6、shiro的session建立時機是在登入認證之前,而不是第一次調用getSession()時。

  《跟我學shiro》

繼續閱讀