关于扩展 Laravel 默认 Session 中间件导致的 Session 写入失效问题分析


Posted in PHP onJanuary 08, 2016

最近由于项目开发需要,手机客户端和网页端统一使用一套接口,为保证 会话(Session) 能够正常且在各类情况下兼容,我希望能够改变 SessionID 的获取方式。默认情况下,所有网站都是通过 HTTP 请求的 Header 头部中的 Cookie 实现的,通过 Cookie 中指定的 SessionID 来关联到服务端对应数据,从而实现会话功能。

但对于手机客户端,可能并不会支持原始的 Cookie,亦或者根据平台需要而屏蔽,因此开发中要求通过增加一个请求头 X-Session-Token 来标识 SessionID。在 Laravel 框架中,实现 Session 初始化、读取和启动,都是通过 Illuminate\Session\Middleware\StartSession 这个中间件实现的,该中间件有一个关键方法 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 方法的第一行代码就是:

$this->sessionHandled = true;

Interesting。。。

我们知道。Laravel 框架的特色是其 IoC 容器,框架中初始化各种类都是由其负责以实现各种依赖注入,以保证组件间的松耦合。中间件定然不例外。要知道,单例和普通实例最大的区别在于无论创建多少次,单例永远都是一个,实例中的属性不会被初始化,因此无问题的中间件必然是一个单例,而我自己创建的中间件只是个普通的类的实例。但本着知其然更要知其所以然,我需要确认我这一想法(其实解决办法已经想到了,后面说)。

那么问题大致就在于初始化中间件这块了,于是不得不打起精神,仔细理一下 Laravel 的启动代码。而这里面的重点,在于一个叫 Illuminate\Pipeline\Pipeline 的类。

这个类有三个重要方法 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 写入失效问题分析的全部内容,希望大家喜欢。

PHP 相关文章推荐
web方式ftp
Oct 09 PHP
简单采集了yahoo的一些数据
Feb 14 PHP
PHP中利用substr_replace将指定两位置之间的字符替换为*号
Jan 27 PHP
PHP中多维数组的foreach遍历示例
Jun 13 PHP
PHP中使用TCPDF生成PDF文档实例
Jul 01 PHP
phpnow php探针环境检测代码
Nov 04 PHP
PHP采用get获取url汉字出现乱码的解决方法
Nov 13 PHP
php检测数组长度函数sizeof与count用法
Nov 17 PHP
老司机传授Ubuntu下Apache+PHP+MySQL环境搭建攻略
Mar 20 PHP
PHP7 新特性详细介绍
Sep 06 PHP
PHP生成加减算法方式的验证码实例
Mar 12 PHP
php使用curl模拟浏览器表单上传文件或者图片的方法
Nov 10 PHP
在PHP站点的页面上添加Facebook评论插件的实例教程
Jan 08 #PHP
理解PHP中的Session及对Session有效期的控制
Jan 08 #PHP
PHP实现搜索地理位置及计算两点地理位置间距离的实例
Jan 08 #PHP
PHP使用数组依次替换字符串中匹配项
Jan 08 #PHP
PHP 7.0.2 正式版发布
Jan 08 #PHP
深入浅析php中sprintf与printf函数的用法及区别
Jan 08 #PHP
PHP中each与list用法分析
Jan 08 #PHP
You might like
php计划任务之ignore_user_abort函数实现方法
2015/01/08 PHP
PHP中soap用法示例【SoapServer服务端与SoapClient客户端编写】
2018/12/25 PHP
PHP标准库(PHP SPL)详解
2019/03/16 PHP
JavaScript入门教程 Cookies
2009/01/31 Javascript
js中关于String对象的replace使用详解
2011/05/24 Javascript
使用js检测浏览器是否支持html5中的video标签的方法
2014/03/12 Javascript
调整小数的格式保留小数点后两位
2014/05/14 Javascript
正则中的回溯定义与用法分析【JS与java实现】
2016/12/27 Javascript
详解angular2封装material2对话框组件
2017/03/03 Javascript
小程序视频列表中视频的播放与停止的示例代码
2018/07/20 Javascript
jQuery实现鼠标拖动图片功能
2021/03/04 jQuery
[17:13]DOTA2 HEROS教学视频教你分分钟做大人-斯拉克
2014/06/13 DOTA
从零学Python之入门(二)基本数据类型
2014/05/25 Python
python执行get提交的方法
2015/04/29 Python
在Django框架中伪造捕捉到的URLconf值的方法
2015/07/18 Python
实例探究Python以并发方式编写高性能端口扫描器的方法
2016/06/14 Python
详解 Python中LEGB和闭包及装饰器
2017/08/03 Python
对Python信号处理模块signal详解
2019/01/09 Python
对PyQt5的输入对话框使用(QInputDialog)详解
2019/06/25 Python
python实现车牌识别的示例代码
2019/08/05 Python
Python 变量的创建过程详解
2019/09/02 Python
python opencv图片编码为h264文件的实例
2019/12/12 Python
使用python-pptx包批量修改ppt格式的实现
2020/02/14 Python
python中format函数如何使用
2020/06/22 Python
导出HTML5 Canvas图片并上传服务器功能
2019/08/16 HTML / CSS
阿迪达斯丹麦官网:adidas丹麦
2016/10/01 全球购物
韩国江南富人区高端时尚百货商场:Galleria(格乐丽雅)
2018/03/27 全球购物
2013年保送生自荐信格式
2013/11/20 职场文书
试用期自我鉴定范文
2014/03/20 职场文书
科技工作者先进事迹
2014/08/16 职场文书
2014年个人业务工作总结
2014/11/17 职场文书
房屋授权无偿使用证明
2014/11/29 职场文书
个人委托函范文
2015/01/29 职场文书
文言文辞职信
2015/02/28 职场文书
背起爸爸上学观后感
2015/06/08 职场文书
阿凡达观后感
2015/06/10 职场文书