关于扩展 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 相关文章推荐
php 常用类汇总 推荐收藏
May 13 PHP
PHP下操作Linux消息队列完成进程间通信的方法
Jul 24 PHP
smarty中英文多编码字符截取乱码问题解决方法
Oct 28 PHP
PHP获取数组中重复最多的元素的实现方法
Nov 11 PHP
PHP实现抓取迅雷VIP账号的方法
Jul 30 PHP
php实现word转html的方法
Jan 22 PHP
无需数据库在线投票调查php代码
Jul 20 PHP
php输出含有“#”字符串的方法
Jan 18 PHP
PHP __call()方法实现委托示例
May 20 PHP
PHP反射原理与用法深入分析
Sep 28 PHP
PHP后门隐藏的一些技巧总结
Nov 04 PHP
一文搞懂PHP中的抽象类和接口
May 25 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
ThinkPHP3.1新特性之对分组支持的改进与完善概述
2014/06/19 PHP
Zend Framework动作助手Url用法详解
2016/03/05 PHP
ThinkPHP3.1.x修改成功与失败跳转页面的方法
2017/09/29 PHP
小程序微信退款功能实现方法详解【基于thinkPHP】
2019/05/05 PHP
Jquery中的层次选择器与find()的区别示例介绍
2014/02/20 Javascript
js 获取浏览器版本以此来调整CSS的样式
2014/06/03 Javascript
js实现完全自定义可带多级目录的网页鼠标右键菜单方法
2015/02/28 Javascript
jQuery插件制作之全局函数用法实例
2015/06/01 Javascript
详解基于javascript实现的苹果系统底部菜单
2016/12/02 Javascript
简单三步实现报表页面集成天气
2016/12/15 Javascript
关于vue.js v-bind 的一些理解和思考
2017/06/06 Javascript
jQuery获取所有父级元素及同级元素及子元素的方法(推荐)
2018/01/21 jQuery
vue2.0 实现页面导航提示引导的方法
2018/03/13 Javascript
vue里如何主动销毁keep-alive缓存的组件
2019/03/21 Javascript
vue watch关于对象内的属性监听
2019/04/22 Javascript
jsonp实现百度下拉框功能的方法分析
2019/05/10 Javascript
手把手15分钟搭一个企业级脚手架
2019/09/16 Javascript
JS设置自定义快捷键并实现图片上下左右移动
2019/10/17 Javascript
js实现select下拉框选择
2020/01/11 Javascript
ES6使用新特性Proxy实现的数据绑定功能实例
2020/05/11 Javascript
vue实现PC端分辨率适配操作
2020/08/03 Javascript
解决vue项目axios每次请求session不一致的问题
2020/10/24 Javascript
Vue-Ant Design Vue-普通及自定义校验实例
2020/10/24 Javascript
HTML元素拖拽功能实现的完整实例
2020/12/04 Javascript
[04:52]DOTA2亚洲邀请赛附加赛 TOP10精彩集锦
2015/01/29 DOTA
理解Python中函数的参数
2015/04/27 Python
python统计文本文件内单词数量的方法
2015/05/30 Python
在Python的Django框架的视图中使用Session的方法
2015/07/23 Python
Python函数参数操作详解
2018/08/03 Python
解决Tensorboard 不显示计算图graph的问题
2020/02/15 Python
COACH德国官方网站:纽约现代奢侈品牌,1941年
2018/06/09 全球购物
环境工程专业自荐信
2014/03/03 职场文书
无婚姻登记记录证明
2015/06/18 职场文书
幼儿园托班开学寄语(2016秋季)
2015/12/03 职场文书
2016年中学植树节活动总结
2016/03/16 职场文书
小喇叭开始广播了! 四十多年前珍贵老照片
2022/05/09 无线电