Safari 不使用 JS Fetch API 设置 CORS cookie

Posted

技术标签:

【中文标题】Safari 不使用 JS Fetch API 设置 CORS cookie【英文标题】:Safari not setting CORS cookies using JS Fetch API 【发布时间】:2017-07-07 05:06:56 【问题描述】:

在使用 Fetch API(实际上是通过 fetch polyfill)时,我无法让 Safari 从服务器响应中成功应用 Set-Cookie。相同的代码在 FF 和 Chrome 中都能正常工作(我使用原生和 polyfill fetch 进行了测试)。

    请求是跨域的; 是的,我正在设置credentials: true; 服务器确实以Set-Cookie 标头响应; 从 Chrome 和 FF 发送带有 cookie 请求标头的后续请求,但 Safari 没有; 请求使用 HTTPS(证书是自签名的,在开发域上,但 Safari 似乎在常规请求中接受);和

有人知道问题可能是什么吗?

我已阅读文档并阅读了许多closed bug reports。除非我错过了什么,否则我认为问题可能出在 'default browser behaviour' 处理 cookie 和 CORS 上——而不是 fetch (阅读 polyfill 源代码,似乎 100% 不了解 cookie)。一些错误报告表明,格式错误的服务器响应可能会阻止保存 cookie。

我的代码如下所示:

function buildFetch(url, init=) 
    let headers = Object.assign(, init.headers || , 'Content-Type': 'application/json');
    let params = Object.assign(, init,  credentials: 'include', headers );

    return fetch(`$baseUrl$url`, params);


buildFetch('/remote/connect', method: 'PUT', body: JSON.stringify( code ))
.then(response => response.json())
.then(/* complete authentication */)

实际的授权请求如下。我正在使用 cURL 来获取确切的请求/响应数据,因为 Safari 很难复制/粘贴它。

curl 'https://mydevserver:8443/api/v1/remote/connect' \
-v \
-XPUT \
-H 'Content-Type: application/json' \
-H 'Referer: http://localhost:3002/' \
-H 'Origin: http://localhost:3002' \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/602.4.8 (Khtml, like Gecko) Version/10.0.3 Safari/602.4.8' \
--data-binary '"token":"value"'


*   Trying 127.0.0.1...
* Connected to mydevserver (127.0.0.1) port 8443 (#0)
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
* Server certificate: mydevserver
> PUT /api/v1/remote/connect HTTP/1.1
> Host: mydevserver:8443
> Accept: */*
> Content-Type: application/json
> Referer: http://localhost:3002/
> Origin: http://localhost:3002
> User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/602.4.8 (KHTML, like Gecko) Version/10.0.3 Safari/602.4.8
> Content-Length: 15
> 
* upload completely sent off: 15 out of 15 bytes
< HTTP/1.1 200 OK
< Access-Control-Allow-Origin: http://localhost:3002
< Access-Control-Allow-Credentials: true
< Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Api-Key, Device-Key
< Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
< Access-Control-Expose-Headers: Date
< Content-Type: application/json; charset=utf-8
< Content-Length: 37
< Set-Cookie: express:sess=[SESSIONKEY]=; path=/; expires=Fri, 17 Feb 2017 15:30:01 GMT; secure; httponly
< Set-Cookie: express:sess.sig=[SIGNATURE]; path=/; expires=Fri, 17 Feb 2017 15:30:01 GMT; secure; httponly
< Date: Fri, 17 Feb 2017 14:30:01 GMT
< Connection: keep-alive
< 
* Connection #0 to host mydevserver left intact
"some":"normal","response":"payload"

【问题讨论】:

对于 2020 年遇到此问题的任何其他人,请确保您测试 Safari 首选项 → 隐私 → 网站跟踪:防止跨站点跟踪。我不得不把它关掉。 Niklas Merz 的测试应用程序niklas.merz.dev/corstestbugs.webkit.org/show_bug.cgi?id=200857 中详述也有助于确保其他一切正常。 启用“防止跨站点跟踪”在我的情况下导致问题。 Safari > 首选项 > 隐私 -> 禁用跨站点跟踪 万岁@AmitSaharan 【参考方案1】:

回答我自己的问题。

尽管我理解他们的动机,但我发现这是 Safari 的“按预期工作”行为,这让我非常愤怒。 XHR(可能是原生获取时的原生获取)根本不支持第三方 cookie 的设置。这种失败是完全透明的,因为它是由脚本上下文之外的浏览器处理的,因此基于客户端的解决方案实际上是不可能的。

您将在此处找到的一个推荐解决方案是打开一个窗口或 iframe 到 API 服务器上的 HTML 页面并在那里设置一个 cookie。此时,第 3 方 cookie 将开始工作。这很丑陋,而且不能保证 Safari 不会在某个时候堵住这个漏洞。

我的解决方案基本上是重新实现一个身份验证系统,该系统执行会话 cookie 的功能。即:

    添加一个新标头X-Auth: [token],其中[token] 是一个非常小的、短暂的 JWT,其中包含您的会话所需的信息(理想情况下只有用户 id - 在生命周期内不太可能发生变化的信息您的应用程序的权限——但如果权限可以在会话期间更改,则绝对不是权限之类的东西); 将X-Auth添加到Access-Control-Allow-Headers; 在登录期间,使用您需要的负载设置会话 cookie 和身份验证令牌(Safari 和非 Safari 用户都将获得 cookie 和身份验证标头); 在客户端上,查找X-Token 响应标头,并在看到它的任何时候将其作为X-Token 请求标头回显(您可以通过使用本地存储实现持久性——令牌过期,所以即使价值生命多年,过了一定时间就无法赎回); 在服务器上,对于所有对受保护资源的请求,检查 cookie,如果存在就使用它; 否则(如果 cookie 不存在 - 因为 Safari 没有发送它),查找标头令牌,验证和解码令牌有效负载,使用提供的信息更新当前会话,然后生成新的身份验证令牌并将其添加到响应标头中; 照常进行。

请注意,JWT(或类似的东西)旨在解决一个完全不同的问题,并且由于“重放”问题,真的不应该用于会话管理(想想如果用户打开两个窗口会发生什么情况标头状态)。然而,在这种情况下,它们提供了您通常需要的短暂性和安全性。最重要的是,您应该在支持它们的浏览器上使用 cookie,使会话信息尽可能小,使您的 JWT 尽可能短,并构建您的服务器应用程序以防意外和恶意重放攻击。

【讨论】:

我也有这个问题。我在没有credentials: 'include' 的情况下使用 Fetch。 Firefox 工作了——我预料到了,因为credentials 似乎是关于发送 凭据而不是接收它们——但 Safari 没有。设置credentials: 'include' 使Safari 工作。 ¯\_(ツ)_/¯ 我在使用 credentials: 'include' 时遇到了故障,但这是由于 Safari IPT(智能跟踪预防)。关闭它会使我的应用使用跨域凭据请求工作。 关闭 IPT 对我来说就像是一种魅力,感谢您发布此内容。【参考方案2】:

仅供参考,大约 18 个月后尝试这个,这个解决方案对我不起作用。或者,它似乎是间歇性的,对于某些用户来说,这真的很奇怪。

您将在此处找到的一个推荐解决方案是打开一个窗口或 iframe 到 API 服务器上的 HTML 页面并在那里设置 cookie。在 此时,第 3 方 cookie 将开始工作。这很丑 并且不能保证 Safari 不会在某个时候关闭 漏洞。

最好的猜测是 Safari 内部使用的逻辑取决于您设法获取 cookie 的顺序,或者更复杂和不透明的东西。

我最终确定的解决方案是使用 DNS,如果您因为您从与 API 不同的主机提供 React 应用程序或以其他方式控制两个站点而遇到此问题,这可能是一个选择:

从 www.company-name.com 为我们的客户提供服务,而我们的 API 在 company-name.herokuapp.com 上。通过创建 CNAME 记录 api.company-name.com --> company-name.herokuapp.com,并将同一域的子域用于来自API 的客户端,Safari 不再将其视为“第三方”cookie。

好处是涉及的代码很少,而且都使用了成熟的东西……坏处是,如果您要使用 https,则需要对 API 主机进行一些控制/所有权 - 他们需要一个对客户端域有效的证书,否则用户将收到证书警告 - 因此,如果相关 API 不是您的或合作伙伴的,这将不起作用(至少不适用于面向最终用户的东西)。

【讨论】:

在 2021 年 Safari 现在似乎阻止 CNAME 伪装时,这仍然有效吗?

以上是关于Safari 不使用 JS Fetch API 设置 CORS cookie的主要内容,如果未能解决你的问题,请参考以下文章

Safari:重新加载后“由于访问控制检查而无法加载 Fetch API”

fetch api和ajax本质的区别

js fetch处理异步请求

React.js:使用 Fetch API 和对象数组中的道具加载 JSON 数据

如何使用 Fetch API 下载和保存文件? (Node.js)

javascript 在Express.JS中的Node.JS中使用Fetch API