首頁 > php教程 > PHP开发 > 主體

關於擴充 Laravel 預設 Session 中介軟體導致的 Session 寫入失效問題分析

高洛峰
發布: 2016-12-27 10:04:12
原創
1073 人瀏覽過

最近由於專案開發需要,手機客戶端和網頁端統一使用一套接口,為保證 會話(Session) 能夠正常且在各類情況下兼容,我希望能夠改變 SessionID 的獲取方式。預設情況下,所有網站都是透過 HTTP 請求的 Header 頭部中的 Cookie 實現的,透過 Cookie 中指定的 SessionID 來關聯到服務端對應數據,從而實現會話功能。

但對於手機用戶端,可能並不會支援原始的 Cookie,也或根據平台需求而屏蔽,因此開發中要求透過增加一個請求頭 X-Session-Token 來識別 SessionID。在 Laravel 框架中,實作 Session 初始化、讀取和啟動,都是透過 IlluminateSessionMiddlewareStartSession 這個中間件實現的,這個中間件有一個關鍵方法 getSession ,這個方法就是取得 SessionId 從而告知 Session 元件以什麼憑證恢復 Session 資料。

此中介軟體註冊於 app/Http/Kernel.php 檔案下。

我新建了一個類別繼承該中間件,同時替換了在app/Http/Kernel.php 下的註冊的地方,原來的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();
// 判断是否是接口访问并根据实际情况选择 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;
}
登入後複製

但是麻煩也隨之而來。 。 。

修改完後,推送至分支,在合併至主開發分支之前往往需要跑一下單元測試,不幸的是,之前通過的Case 這回竟然報錯,問題是CSRF 組件報出Token 錯誤,而我們在這一處提供的Token 跟平時並無二致,問題肯定出在Session 上。

值得注意的是,我修改中間件的程式碼,對框架的影響可以說根本沒有,事實上也確實沒有,因為我將我自己創建的中間件代碼修改成繼承的中間件代碼一致也無濟於事,但奇怪的是,在我將中間件換回原來的中間件就沒有這個問題。

於是我將正常情況下和非正常情況下的程式碼都跑了一遍,在關鍵處斷點調試,發現問題出在中間件的一個重要屬性$sessionHandled , 若該值為false 則會引起我們之前的狀況。關鍵在於,中間件啟動之時,都會走 handle 方法,而對於 Session 這個中間件, handle 方法的第一行程式碼就是:

Interesting。 。 。

我們知道。 Laravel 框架的特色是其 IoC 容器,框架中初始化各種類別都是由其負責以實現各種依賴注入,以確保組件間的松耦合。中間件定然不例外。要知道,單例和普通實例最大的區別在於無論創建多少次,單例永遠都是一個,實例中的屬性不會被初始化,因此無問題的中間件必然是一個單例,而我自己創建的中間件只是個普通的類別的實例。但本著知其然更要知其所以然,我需要確認我這一想法(其實解決方法已經想到了,後面說)。

那麼問題大致就在於初始化中間件這塊了,於是不得不打起精神,仔細理一下 Laravel 的啟動程式碼。而這裡面的重點,在於一個叫做 IlluminatePipelinePipeline 的類別。

這個類別有三個重要方法 send 、 through 、 then 。其中 then 是開始一切的鑰匙。這個類別主要是連續執行幾個框架啟動步驟的玩意兒,首先是初始化處理過程所需的元件(Request 和中間件),其次是將請求透過這些處理元件構成的堆疊(一堆中間件和路由派發元件),最後是返回處理結果(Response)。

可以說這玩意兒是 Laravel Http 部分的核心(額,,本來就是 Kernel)。那麼之前的問題就在於Pipeline 的then 方法和其調用的getSlice 方法,直接觀察getSlice 方法,可以發現它負責的是生成處理堆棧,並實例化Middleware (中間件)類,整個方法代碼如下:

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) ,這意味著其初始化一個中間件類,單純的就是make,若其不是單例則反覆new ,導致之前的屬性被初始化。

那麼解決辦法也顯而易見,使其成為一個單例。

我在app/Providers/AppServiceProvider.php 的register 方法中加入如下一行程式碼,就解決了先前的問題:

$this->app->singleton(SessionStart::class); // SessionStart 是我那個中間件類別名稱

以上給大家介紹了擴充Laravel 預設Session 中間件導致的Session 寫入失效問題分析的全部內容,希望大家喜歡。

更多關於擴展 Laravel 預設 Session 中間件導致的 Session 寫入失效問題分析相關文章請關注PHP中文網!

來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門推薦
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板