關(guān)于擴(kuò)展 Laravel 默認(rèn) Session 中間件導(dǎo)致的 Session 寫入失效問題分析
最近由于項(xiàng)目開發(fā)需要,手機(jī)客戶端和網(wǎng)頁(yè)端統(tǒng)一使用一套接口,為保證 會(huì)話(Session) 能夠正常且在各類情況下兼容,我希望能夠改變 SessionID 的獲取方式。默認(rèn)情況下,所有網(wǎng)站都是通過 HTTP 請(qǐng)求的 Header 頭部中的 Cookie 實(shí)現(xiàn)的,通過 Cookie 中指定的 SessionID 來關(guān)聯(lián)到服務(wù)端對(duì)應(yīng)數(shù)據(jù),從而實(shí)現(xiàn)會(huì)話功能。
但對(duì)于手機(jī)客戶端,可能并不會(huì)支持原始的 Cookie,亦或者根據(jù)平臺(tái)需要而屏蔽,因此開發(fā)中要求通過增加一個(gè)請(qǐng)求頭 X-Session-Token 來標(biāo)識(shí) SessionID。在 Laravel 框架中,實(shí)現(xiàn) Session 初始化、讀取和啟動(dòng),都是通過 Illuminate\Session\Middleware\StartSession 這個(gè)中間件實(shí)現(xiàn)的,該中間件有一個(gè)關(guān)鍵方法 getSession ,這個(gè)方法就是獲取 SessionId 從而告知 Session 組件以什么憑據(jù)恢復(fù) Session 數(shù)據(jù)。
該中間件注冊(cè)于 app/Http/Kernel.php 文件下。
我新建了一個(gè)類繼承該中間件,同時(shí)替換了在 app/Http/Kernel.php 下的注冊(cè)的地方,原來的 getSession 方法源碼如下:
public function getSession(Request $request) { $session = $this->manager->driver(); $session->setId($request->cookies->get($session->getName())); return $session; }
在新的中間件中,我修改為:
public function getSession(Request $request) { $session = $this->manager->driver(); // 判斷是否是接口訪問并根據(jù)實(shí)際情況選擇 SessionID 的獲取方式 if ($request->headers->has('x-session-token')) { $sessionId = $request->headers->has('x-session-token'); } else { $sessionId = $request->cookies->get($session->getName()); } $session->setId($sessionId); return $session; }
但是麻煩也隨之而來。。。
修改完后,推送至分支,在合并至主開發(fā)分支之前往往需要跑一下單元測(cè)試,不幸的是,之前通過的 Case 這回竟然報(bào)錯(cuò),問題是 CSRF 組件 報(bào)出 Token 錯(cuò)誤,而我們?cè)谶@一處提供的 Token 跟平時(shí)并無二致,問題肯定出在 Session 上。
值得注意的是,我修改中間件的代碼,對(duì)框架的影響可以說根本沒有,事實(shí)上也確實(shí)沒有,因?yàn)槲覍⑽易约簞?chuàng)建的中間件代碼修改成繼承的中間件代碼一致也無濟(jì)于事,但奇怪的是,在我將中間件換回原來的中間件就沒有這個(gè)問題。
于是我將正常情況下和非正常情況下的代碼都跑了一遍,在關(guān)鍵處斷點(diǎn)調(diào)試,發(fā)現(xiàn)問題出在中間件的一個(gè)重要屬性 $sessionHandled , 若該值為 false 則會(huì)引起我們之前的狀況。關(guān)鍵在于,中間件啟動(dòng)之時(shí),都會(huì)走 handle 方法,而對(duì)于 Session 這個(gè)中間件, handle 方法的第一行代碼就是:
$this->sessionHandled = true;
Interesting。。。
我們知道。Laravel 框架的特色是其 IoC 容器,框架中初始化各種類都是由其負(fù)責(zé)以實(shí)現(xiàn)各種依賴注入,以保證組件間的松耦合。中間件定然不例外。要知道,單例和普通實(shí)例最大的區(qū)別在于無論創(chuàng)建多少次,單例永遠(yuǎn)都是一個(gè),實(shí)例中的屬性不會(huì)被初始化,因此無問題的中間件必然是一個(gè)單例,而我自己創(chuàng)建的中間件只是個(gè)普通的類的實(shí)例。但本著知其然更要知其所以然,我需要確認(rèn)我這一想法(其實(shí)解決辦法已經(jīng)想到了,后面說)。
那么問題大致就在于初始化中間件這塊了,于是不得不打起精神,仔細(xì)理一下 Laravel 的啟動(dòng)代碼。而這里面的重點(diǎn),在于一個(gè)叫 Illuminate\Pipeline\Pipeline 的類。
這個(gè)類有三個(gè)重要方法 send 、 through 、 then 。其中 then 是開始一切的鑰匙。這個(gè)類主要是連續(xù)執(zhí)行幾個(gè)框架啟動(dòng)步驟的玩意兒,首先是初始化處理過程需要的組件(Request 和 中間件),其次是將請(qǐng)求通過這些處理組件構(gòu)成的堆棧(一堆中間件和路由派發(fā)組件),最后是返回處理結(jié)果(Response)。
可以說這玩意兒是 Laravel Http 部分的核心(額,,本來就是 Kernel)。那么之前的問題就在于 Pipeline 的 then 方法和其調(diào)用的 getSlice 方法,直接觀察 getSlice 方法,可以發(fā)現(xiàn)它負(fù)責(zé)的是生成處理堆棧,并實(shí)例化 Middleware (中間件)類,整個(gè)方法代碼如下:
protected function getSlice() { return function ($stack, $pipe) { return function ($passable) use ($stack, $pipe) { if ($pipe instanceof Closure) { return call_user_func($pipe, $passable, $stack); } else { list($name, $parameters) = $this->parsePipeString($pipe); return call_user_func_array([$this->container->make($name), $this->method], array_merge([$passable, $stack], $parameters)); } }; }; }
可以注意到 $this->container->make($name) ,這意味著其初始化一個(gè)中間件類,單純的就是 make,若其不是單例則反復(fù) new ,導(dǎo)致之前的屬性被初始化。
那么解決辦法也顯而易見面,使其成為一個(gè)單例。
我在 app/Providers/AppServiceProvider.php 的 register 方法中添加如下一行代碼,就解決了之前的問題:
$this->app->singleton(SessionStart::class); // SessionStart 是我那個(gè)中間件類名
以上給大家介紹了擴(kuò)展 Laravel 默認(rèn) Session 中間件導(dǎo)致的 Session 寫入失效問題分析的全部?jī)?nèi)容,希望大家喜歡。
- 異步 HttpContext.Current實(shí)現(xiàn)取值的方法(解決異步Application,Session,Cache...等失效的問題)
- 瀏覽器關(guān)閉使session失效的問題多種解決方式
- iframe跨域與session失效問題的解決辦法
- Laravel 5框架學(xué)習(xí)之Eloquent (laravel 的ORM)
- Laravel 5框架學(xué)習(xí)之表單
- Laravel 5框架學(xué)習(xí)之日期,Mutator 和 Scope
- Laravel 5框架學(xué)習(xí)之表單驗(yàn)證
- Laravel 5 框架入門(一)
- Session對(duì)象失效的客戶端解決方法
相關(guān)文章
詳解PHP實(shí)現(xiàn)支付寶小程序用戶授權(quán)的工具類
這篇文章主要介紹了詳解PHP實(shí)現(xiàn)支付寶小程序用戶授權(quán)的工具類,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-12-12Yii框架ACF(accessController)簡(jiǎn)單權(quán)限控制操作示例
這篇文章主要介紹了Yii框架ACF(accessController)簡(jiǎn)單權(quán)限控制操作,結(jié)合實(shí)例形式分析了Yii框架簡(jiǎn)單權(quán)限控制操作參數(shù)設(shè)置與使用技巧,需要的朋友可以參考下2019-04-04Zend Framework教程之Zend_Helpers動(dòng)作助手ViewRenderer用法詳解
這篇文章主要介紹了Zend Framework教程之Zend_Helpers動(dòng)作助手ViewRenderer用法,結(jié)合實(shí)例形式較為詳細(xì)的分析了Zend_Helpers動(dòng)作助手ViewRenderer的功能、使用方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下2016-07-07uni-app結(jié)合PHP實(shí)現(xiàn)單用戶登陸demo及解析
這篇文章主要為大家介紹了uni-app結(jié)合PHP實(shí)現(xiàn)單用戶登陸示例過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05在laravel中實(shí)現(xiàn)將查詢的對(duì)象轉(zhuǎn)換為多維數(shù)組的函數(shù)
今天小編就為大家分享一篇在laravel中實(shí)現(xiàn)將查詢的對(duì)象轉(zhuǎn)換為多維數(shù)組的函數(shù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-10-10laravel使用Redis實(shí)現(xiàn)網(wǎng)站緩存讀取的方法詳解
這篇文章主要給大家介紹了關(guān)于laravel使用Redis實(shí)現(xiàn)網(wǎng)站緩存讀取的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03laravel-admin 管理平臺(tái)獲取當(dāng)前登陸用戶信息的例子
今天小編就為大家分享一篇laravel-admin 管理平臺(tái)獲取當(dāng)前登陸用戶信息的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-10-10