缘由

最近开发新项目,在过程中遇到session获取数据为空的问题。查阅很多资料,现在做一下记录。由于将TP6升级至TP8,composer部分插件并不支持TP8,故而需要更换。其中管理页面的验证码图片edward1108/edward-captcha 插件就是这样。

在将整体升级后,这个组件会由于php8的特性,无法继续使用动态属性,故而报错。去packagist.org上寻找替代品,发现了tp官方有相应的拓展包,topthink/think-captcha便改用了TP的拓展包。根据教程将app\middleware.php中的\think\middleware\SessionInit::class注释解除,然后开始调试代码。

// 生成验证码代码替换
//$data = edward\captcha\facade\CaptchaApi::create()

think\captcha\facade\Captcha::create()
$data = Captcha::create();
$data = [                      //为了兼容,为了不修改前端代码
    'base64' => $data['img'],
    'md5' => md5($data['code']),
    'key' => md5($data['code'])
];
return $data;

// 校验验证码代码替换
//edward\captcha\facade\CaptchaApi::check($code, $key)

think\captcha\facade\Captcha::check($code);

发现问题

在解决完上述切换验证码组件的问题后,界面显示了验证码图片,本以为一切完美。结果,在校验时,却怎么都无法校验成功。由于之前有遇到过相同的情况,很快就联想到了是cookie问题。

通过图片可以看到,php是响应了Set-Cookie头属性的,并设置了sessionid。然而在下面图片中的cookie表中完全没看到。

解题过程

Thinkphp项目中存在config/cookie.php文件,文件中可见如下设置选项:

return [
    // cookie 保存时间
    'expire'    => 0,
    // cookie 保存路径
    'path'      => '/',
    // cookie 有效域名
    'domain'    => 'nutrition.cc',
    //  cookie 启用安全传输
    'secure'    => false,
    // httponly设置
    'httponly'  => true,
    // 是否使用 setcookie
    'setcookie' => true,
    // samesite 设置,支持 'strict' 'lax'
    'samesite'  => 'strict',
];

HttpOnly

HttpOnly 字段  cookie的httponly属性。若此属性为true,则只有在http请求头中会带有此cookie的信息,而不能通过 document.cookie 来访问此 cookie。这样能有效的防止XSS攻击。窃取cookie内容,这样就增加了cookie的安全性。1

也就是说如果设置为true,则前端代码无法获取到该cookie,且无法操作该cookie。

Secure

secure属性可防止信息在传递的过程中被监听捕获后导致信息泄露,如果设置为true,可以限制只有通过https访问时,才会将浏览器保存的cookie传递到服务端,如果通过http访问,不会传递cookie。2

如果设置为true,则这个cookie只在https的站点中生效,http无效。

SameSite

这项可以设置三个选项:

Strict

Strict最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。3

这个选项必须是域名(包含子域名)完全一致,才可以传递cookie。

Lax

Lax规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。3

请求类型

示例

正常情况

Lax

链接

<a href="..."></a>

发送 Cookie

发送 Cookie

预加载

<link rel="prerender" href="..."/>

发送 Cookie

发送 Cookie

GET 表单

<form method="GET" action="...">

发送 Cookie

发送 Cookie

POST 表单

<form method="POST" action="...">

发送 Cookie

不发送

iframe

<iframe src="..."></iframe>

发送 Cookie

不发送

AJAX

$.get("...")

发送 Cookie

不发送

Image

<img src="...">

发送 Cookie

不发送

None

Chrome 计划将Lax变为默认设置。这时,网站可以选择显式关闭SameSite属性,将其设为None。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。3

None是最宽松的控制项,但不利于开发环境,因为该选项必须配合Secure=true来使用,也就是网站必须开通https功能。

Domain

Domain 指定了哪些主机可以接受 Cookie。如果不指定,该属性默认为同一 host 设置 cookie,不包含子域名。如果指定了 Domain,则一般包含子域名。因此,指定 Domain 比省略它的限制要少。但是,当子域需要共享有关用户的信息时,这可能会有所帮助。4

但经过多次测试,暂时无法实现子域名之间的cookie传递。

解决方法

目前唯一能解决的方法,就是做nginx代理,将域名下的某一个子目录代理为前端代码。

感谢:

1.cookie中数据无法读取,HttpOnly属性 - 马文庆i - 博客园

2.HTTP Cookie header 中set-cookie格式及安全secure httponly - silyvin - 博客园

3.Cookie 的 SameSite 属性 - 阮一峰的网络日志

4.HTTP Cookie - HTTP | MDN