怎么会是跨域的问题
跨域这个问题已经是老生常谈了,我要是看到这两个字,我的第一反应肯定是搞一搞应该马上就解决了,不需要花什么精力在这上面。不过这次还是啪啪打脸,因为确实花了一些时间来解决一个跨域且带 Cookie 的问题。也好久没调试前端相关的内容了,本文做一个简单的记录,因为过程实在是挺有意思的。
我的使用场景
我碰到的问题,场景是 Chrome 浏览器插件内,跨域带 Cookie。首先,这是一个远古项目了,很多东西传了一代又一代,到我这时,代码已经十分“美妙”。浏览器插件加载 Background 时,引入了一个上传到阿里云 OSS 的 JS 文件,JS 文件中调用了后端一些接口,Ajax 的形式,或者 iframe 再嵌入一个后端链接直接渲染,而后端的实现是传统的 Session 登录态,前端调用的接口都是有状态的,如果没有登录后台系统,会重定向到一个登录服务链接(不支持跨域)。那么这个实现就要求我们必须跨域时必须带上 Cookie。听起来有点麻烦,感觉挺难搞的。但是你思考一下,好像也简单。
首先是前端跨域带 Cookie,我们的常规做法,JQuery 这里:
$.ajax({
type:'get',
crossDomain:true, // 设置允许跨域
xhrFields: {
withCredentials: true // 设置带上 cookie
},
}})
我们只需要加上 crossDomain
和 withCredentials
就行了。
后端,比如 PHP 中,通常我们会设置:
header("Access-Control-Allow-Origin: *");
但是这里不行。我们为了搭配前端的 withCredentials
,需要设置 Access-Control-Allow-Credentials
以及 Access-Control-Allow-Headers
不能为 *
,例如:
header("Access-Control-Allow-Headers: *");
header("Access-Control-Allow-Credentials: true");
header("Access-Control-Allow-Methods: POST,GET,OPTIONS");
header("Cache-Control: no-cache");
header("Content-Type: application/json");
$origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '';
$allowOrigin = [
'https://item.taobao.com',
'https://detail.tmall.com'
];
if (in_array($origin, $allowOrigin)) {
header("Access-Control-Allow-Origin:" . $origin);
} else {
header("Access-Control-Allow-Origin: *");
}
那么,这个时候我们应该就能跨域且带 Cookie 了。
Emmmm~~显然没那么简单,穿梭在各种开发测试环境中 Debug 的过程简直是折磨,特别是对于一个后端开发来说,但是照着红字报错 Debug 总没错。
Chrome 80 SameSite 属性
然后我就发现了 Chrome 80 的改动,很久没写前端了,确实没关注。
在 Chrome 80 中,Chrome 的 SameSite 会有这样的变动:
- 默认情况下,所有未指定 SameSite 属性的 cookie 将自动强制设置为 SameSite=Lax
- SameSite=None 的 Cookie 必须安全,就是说只有采用 SameSite=None; Secure 设置的 cookie 可以从外部通过 HTTPS 访问
查了一下 SameSite 可以有三个不同的值:Strict、Lax 或 None。
值 | 描述 |
---|---|
Strict | 只有在访问最初设置的域时,才可访问具有此设置的 Cookie。换言之,Strict 会完全阻止跨站点使用 Cookie。这种选择最适用于需要高安全性的应用,如银行。 |
Lax | 具有此设置的Cookie仅在同一站点请求或具有非幂等HTTP请求的顶级导航上发送,如 HTTP GET 。 因此,如果第三方可以使用 cookie,但增加了安全优势,保护用户免受CSRF攻击的侵害,则将使用此选项。 |
None | 使用此设置的 Cookie 将像 Cookie 之前工作的方式一样工作。 |
那么,这个规则一出来,肯定会影响一些以前的应用。
我找到一个表格,非常有意思,SameSite 在不同的应用场景对比新默认策略和之前的不同之处:
请求类型 | 示例 | SameSite=None | SameSite=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 | 不发送 |
很明显,iframe 和 Ajax,已经不能携带 Cookie 了,即使你做了一波上面的操作。那么这个时候我们要怎么做呢?
解决方案
查了一些资料,什么修改浏览器配置这些就不要看了,不是解决问题的办法。要解决这个问题,似乎也很简单。
首先修改 Cookie 的设置,需要设置 SameSite=None; Secure
,具体的怎么设置,查一下你使用的框架就好,如果是原生 PHP,PHP 官方也有 SameSite 的说明。
这里举一个 Yii 的例子:
$session = [
'class' => 'yii\redis\Session',
'timeout' => 3600 * 16,
'cookieParams' => [
'httponly' => true,
'secure' => true,
'path' => '/',
'sameSite' => "None",
],
];
Component 中看情况设置 secure
和 sameSite
就行。
后端 Cookie 设置完,前端用上 HTTPS,这里要注意的是,需要是你调用接口的脚本也得是 HTTPS。举几个例子,关注一下中间那个环节:
- 源站点 -> 加载 JS 文件(HTTPS) -> JS 中调用 Ajax
- 源站点 -> Get 后端接口(HTTPS)返回 IFrame 的 HTML -> IFrame 中链接另一个后端渲染页面(HTTPS)
- 源站点 -> 加载 CDN 中的 JS 文件 (HTTPS)-> JS 中调用 Ajax
在设置了 secure
的 Cookie 后,我们要带上 Cookie 就必须上 HTTPS,否则就带不过去,而且会很迷惑地看似应该能带过去实则没带。Cookie 带不过去,登录态没获取到,就重定向跳转了。其实 IFrame 的情况反而更好解决,因为在中间过程中不是跨域了。
小提示
这里给一些过程中的小提示。
在调试的过程中。我们需要用到本地 HTTPS 去调试,否则很麻烦。推荐一个生成本地自签证书的小工具 mkcert,基本上就是一键 HTTPS 了,挺好用。
用以下命令:
mkcert -install
就能生本地 CA 了,拿着证书就能部署了。不管是 Nginx Server 还是其它,都能用。
顺便提一下 VS Code 的 Live Server 插件,一键 server 非常好用,配置如下:
{
"liveServer.settings.https": {
"enable": true, //set it true to enable the feature.
"cert": "~/primary.crt", //full path of the certificate
"key": "~/private.key", //full path of the private key
"passphrase": "12345"
}
}
还有一个提示,在本地开发的过程中,非常容易忽略,配置的本地 HOST 会给你造成调通了的假象。比如。我本地直接 serve 我的 JS 进行调试(这样好调试),那么我的 JS 会是 https://127.0.0.1/..../global.js
这样的路径,也许你还会配置了一个本地的虚拟域名,比如 https://baidu.taobao
,指向本地 127.0.0.1,然后你就开始在 JS 和后端之间进行调试了,调通了,上线了,崩了!为什么呢?本地虚拟域名,还是 127.0.0.1,所以调用过程中,没有跨域哦,调通了实际上不一定就通了。
总结
Token 验证不香吗?搁这整啥 Cookie 呢。
0 条评论
来做第一个留言的人吧!