天天看點

springmvc內建shiro後,session、request姓汪還是姓蔣?

1. 疑問

我們在項目中使用了spring mvc作為mvc架構,shiro作為權限控制架構,在使用過程中慢慢地産生了下面幾個疑惑,本篇文章将會帶着疑問慢慢地解析shiro源碼,進而解開心裡面的那點小糾糾。

(1) 在spring controller中,request有何不同呢 ?

于是,在controller中列印了request的類對象,發現request對象是org.apache.shiro.web.servlet.shirohttpservletrequest ,很明顯,此時的 request 已經被shiro包裝過了。

(2)衆所周知,spring mvc整合shiro後,可以通過兩種方式擷取到session:

通過spring mvc中controller的request擷取session

通過shiro擷取session

那麼,問題來了, 兩種方式擷取的session是否相同呢 ?

這裡需要看一下項目中的shiro的securitymanager配置,因為配置影響了shiro session的來源。這裡沒有配置session管理器。

在controller中再次列印了session,發現前者的session類型是 org.apache.catalina.session.standardsessionfacade ,後者的session類型是org.apache.shiro.subject.support.delegatingsubject$stoppingawareproxiedsession。

很明顯,前者的session是屬于httpservletrequest中的httpsession,那麼後者呢?仔細看stoppingawareproxiedsession,它是屬于shiro自定義的session的子類。通過這個代理對象的源碼,我們發現所有與session相關的方法都是通過它内部委托類delegate進行的,通過斷點,可以看到delegate的類型其實也是 org.apache.catalina.session.standardsessionfacade ,也就是說,兩者在操作session時,都是用同一個類型的session。那麼它什麼時候包裝了httpsession呢?

springmvc內建shiro後,session、request姓汪還是姓蔣?

2. 一起一層一層剝開它的芯

2.1 怎麼擷取過濾器filter

spring mvc 整合shiro,需要在web.xml中配置該filter

delegatingfilterproxy 是一個過濾器,準确來說是目的過濾器的代理,由它在dofilter方法中,擷取spring 容器中的過濾器,并調用目标過濾器的dofilter方法,這樣的好處是,原來過濾器的配置放在web.xml中,現在可以把filter的配置放在spring中,并由spring管理它的生命周期。另外,delegatingfilterproxy中的targetbeanname指定需要從spring容器中擷取的過濾器的名字,如果沒有,它會以filtername過濾器名從spring容器中擷取。

springmvc內建shiro後,session、request姓汪還是姓蔣?

2.2 request的來源

前面說 delegatingfilterproxy 會從spring容器中擷取名為 targetbeanname 的過濾器。接下來看下spring配置檔案,在這裡定義了一個shiro filter的工廠 org.apache.shiro.spring.web.shirofilterfactorybean。

熟悉spring 的應該知道,bean的工廠是用來生産相關的bean,并把bean注冊到spring容器中的。通過檢視工廠bean的getobject方法,可知,委托類調用的filter類型是springshirofilter。接下來我們看一下類圖,了解一下它們之間的關系。

springmvc內建shiro後,session、request姓汪還是姓蔣?

既然springshirofilter屬于過濾器,那麼它肯定有一個dofilter方法,dofilter由它的父類 onceperrequestfilter 實作。onceperrequestfilter 在dofilter方法中,判斷是否在request中有"already filtered"這個屬性設定為true,如果有,則交給下一個過濾器,如果沒有就執行 dofilterinternal( ) 抽象方法。

dofilterinternal由abstractshirofilter類實作,即springshirofilter的直屬父類實作。dofilterinternal 一些關鍵流程如下:

在dofilterinternal中,可以看到對servletrequest和servletreponse進行了包裝。除此之外,還把包裝後的request/response作為參數,建立subject,這個subject其實是代理類delegatingsubject。

那麼,這個包裝後的request是什麼呢?我們繼續解析prepareservletrequest。

繼續包裝request,看下wrapservletrequest方法。無比興奮啊,文章前面的shirohttpservletrequest終于出來了,我們在controller中擷取到的request就是它,是它,它。它是servlet的httpservletrequestwrapper的子類。

shirohttpservletrequest構造方法的第三個參數是個關鍵參數,我們先不管它怎麼來的,進shirohttpservletrequest裡面看看它有什麼用。它主要在兩個地方用到,一個是getrequestedsessionid(),這個是擷取sessionid的方法;另一個是getsession(),它是擷取session會話對象的。

先來看一下getrequestedsessionid()。ishttpsessions決定sessionid是否來自servlet。

再看一下getsession()。ishttpsessions決定了session是否來自servlet。

既然ishttpsessions()那麼重要,我們還是要看一下在什麼情況下,它傳回true。

ishttpsessions是否傳回true是由使用的shiro安全管理器的 ishttpsessionmode() 決定的。回到前面,我們使用的安全管理器是 defaultwebsecuritymanager ,我們看一下 defaultwebsecuritymanager 的源碼,找到 ishttpsessionmode 方法。可以看到,sessionmanager 的類型和 isservletcontainersessions() 起到了決定性的作用。

在配置檔案中,我們并沒有配置sessionmanager ,安全管理器會使用預設的會話管理器 servletcontainersessionmanager,在 servletcontainersessionmanager 中,isservletcontainersessions 傳回 true 。

是以,在前面的shiro配置的情況下,request中擷取的session将會是servlet context下的session。

2.3 subject的session來源

前面 dofilterinternal 的分析中,還落下了subject的建立過程,接下來我們解析該過程,進而揭開通過subject擷取session,這個session是從哪來的。

回憶下,在controller中怎麼通過subject擷取session。

我們看一下shiro定義的session類圖,它們具有一些與 httpsession 相同的方法,例如 setattribute 和 getattribute。

springmvc內建shiro後,session、request姓汪還是姓蔣?

還記得在 dofilterinternal 中,shiro把包裝後的request/response作為參數,建立subject嗎

subject的建立時序圖

springmvc內建shiro後,session、request姓汪還是姓蔣?

最終,由 defaultwebsubjectfactory 建立subject,并把 principals, session, request, response, securitymanager這些參數封裝到subject。由于第一次建立session,此時session沒有執行個體。

那麼,當我們調用 subject .getsession() 嘗試擷取會話session時,發生了什麼呢。從前面的代碼可以知道,我們擷取到的subject是 webdelegatingsubject 類型的,它的父類 delegatingsubject 實作了getsession 方法,下面的代碼是getsession方法中的關鍵步驟。

接下來解析一下,安全管理器根據會話上下文建立session這個流程,追蹤代碼後,可以知道它其實是交由 sessionmanager 會話管理器進行會話建立,由前面的代碼可以知道,這裡的sessionmanager 其實是 servletcontainersessionmanager類,找到它的 createsession 方法。

這裡就可以知道,其實session是來源于 request 的 httpsession,也就是說,來源于上一個過濾器中request的httpsession。httpsession 以成員變量的形式存在 httpservletsession 中。回憶前面從安全管理器擷取 httpservletsession 後,還調用 decorate() 裝飾該session,裝飾後的session類型是 stoppingawareproxiedsession,httpservletsession 是它的成員 。

在文章一開始的時候,通過debug就已經知道,當我們通過 subject.getsession() 擷取的就是 stoppingawareproxiedsession,可見,這與前面分析的是一緻的 。

那麼,當我們通過session.getattribute和session.addattribute時,stoppingawareproxiedsession 做了什麼?它是由父類 proxiedsession 實作 session.getattribute和session.addattribute 方法。我們看一下 proxiedsession 相關源碼。

可見,getattribute 和 addattribute 由委托類delegate完成,這裡的delegate就是httpservletsession 。接下來看 httpservletsession 的相關方法。

此處的httpsession就是之前從httpservletrequest擷取的,也就是說,通過request.getseesion()與subject.getseesion()擷取session後,對session的操作是相同的。

結論

(1)controller中的request,在shiro過濾器中的dofilterinternal方法,将被包裝為shirohttpservletrequest 。

(2)在controller中,通過 request.getsession(_) 擷取會話 session ,該session到底來源servletrequest 還是由shiro管理并管理建立的會話,主要由 安全管理器 securitymanager 和 sessionmanager 會話管理器決定。

(3)不管是通過 request.getsession或者subject.getsession擷取到session,操作session,兩者都是等價的。在使用預設session管理器的情況下,操作session都是等價于操作httpsession。

<a href="https://my.oschina.net/thinwonton/blog/979118">原文連結</a>