shiro原始碼篇 - shiro的session共享,你值得擁有
前言
開心一刻
老師對小明說:"乳就是小的意思,比如乳豬就是小豬,乳名就是小名,請你用乳字造個句"
小明:"我家很窮,只能住在40平米的乳房"
老師:"..., 這個不行,換一個"
小明:"我每天上學都要跳過我家門口的一條乳溝"
老師:"......, 這個也不行,再換一個"
小明:"老師,我想不出來了,把我的乳頭都想破了!"
路漫漫其修遠兮,吾將上下而求索!
github:https://github.com/youzhibing
碼雲(gitee):https://gitee.com/youzhibing
前情回顧
為何需要session共享
如果是單機應用,那麼談不上session共享,session放哪都無所謂,不在乎放到預設的servlet容器中,還是抽出來放到單獨的地方;
也就是說session共享是針對叢集(或分散式、或分散式叢集)的;如果不做session共享,仍然採用預設的方式(session存放到預設的servlet容器),當我們的應用是以叢集的方式釋出的時候,同個使用者的請求會被分發到不同的叢集節點(分發依賴具體的負載均衡規則),那麼每個處理同個使用者請求的節點都會重新生成該使用者的session,這些session之間是毫無關聯的。那麼同個使用者的請求會被當成多個不同使用者的請求,這肯定是不行的。
如何實現session共享
實現方式其實有很多,甚至可以不做session共享,具體有哪些,大家自行去查資料。本文提供一種方式:redis實現session共享,就是將session從servlet容器抽出來,放到redis中儲存,所有叢集節點都從redis中對session進行操作。
SessionDAO
SessionDAO其實是用於session持久化的,但裡面有快取部分,具體細節我們往下看
shiro已有SessionDAO的實現如下
SessionDAO介面提供的方法如下
View Code
SessionDAO給出了從持久層(一般而言是關係型資料庫)操作session的標準。
AbstractSessionDAO提供了SessionDAO的基本實現,如下
View Code
SessionDao的基本實現,實現了SessionDao的create、readSession(具體還是依賴AbstractSessionDAO子類的doCreate、doReadSession實現);同時加入了自己的sessionId生成器,負責sessionId的操作。
CachingSessionDAO提供了session快取的功能,如下
View Code
是應用層與持久化層之間的快取層,不用頻繁請求持久化層以提升效率。重寫了AbstractSessionDAO中的create、readSession方法,實現了SessionDAO中的update、delete、getActiveSessions方法,預留doUpdate和doDelele給子類去實現(doXXX方法操作的是持久層)
MemorySessionDAO,SessionDAO的簡單記憶體實現,如下
View Code
將session儲存在記憶體中,儲存結構是ConcurrentHashMap;專案中基本不用,即使我們不實現自己的SessionDAO,一般用的也是EnterpriseCacheSessionDAO。
EnterpriseCacheSessionDAO,提供了快取功能的session維護,如下
View Code
設定了預設的快取管理器(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共享
共享實現
shiro的session共享其實是比較簡單的,重寫CacheManager,將其操作指向我們的redis,然後實現我們自己的CachingSessionDAO定製快取操作和快取持久化。
自定義CacheManager
ShiroRedisCacheManager
View Code
ShiroRedisCache
View Code
自定義CachingSessionDAO
繼承EnterpriseCacheSessionDAO,然後重新設定其CacheManager(替換掉預設的記憶體快取器),這樣也可以實現我們的自定義CachingSessionDAO,但是這是優選嗎;如若我們實現持久化,繼承EnterpriseCacheSessionDAO是優選,但如果只是實現session快取,那麼CachingSessionDAO是優選,自定義更靈活。那麼我們還是繼承CachingSessionDAO來實現我們的自定義CachingSessionDAO
ShiroSessionDAO
View Code
最後將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方法如下
View Code
上圖中,我可以看到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》
來源:https://www.cnblogs.com/youzhibing/p/9749427.html