Safari 不使用 JS Fetch API 设置 CORS cookie
我无法让 Safari 在使用 Fetch API(实际上是通过 fetch polyfill).相同的代码在 FF 和 Chrome 中都能正常工作(我使用原生和 polyfill fetch
进行了测试).
I am unable to get Safari to successfully apply Set-Cookie
from server responses when using the Fetch API (actually, via the fetch polyfill). The same code works correctly in FF and Chrome (I tested using both native and polyfill fetch
).
- 请求是跨域的;
- 是的,我正在设置
credentials: true
; - 服务器确实使用
Set-Cookie
标头进行响应; - 从 Chrome 和 FF 发送带有 cookie 请求标头的后续请求,但 Safari 没有;
- 请求使用 HTTPS(证书是自签名的,并且在开发域中,但 Safari 似乎在常规请求中接受);和
有人知道问题可能是什么吗?
Does someone know what the problem might be?
我已通读文档并查看了许多已关闭的错误报告.除非我错过了什么,否则我认为问题可能出在 '默认浏览器行为' 处理上使用 cookie 和 CORS——而不是使用 fetch(阅读 polyfill 源代码,似乎 100% 不了解 cookie).一些错误报告表明,格式错误的服务器响应可能会阻止保存 cookie.
I've read through the documentation and gone through many of the closed bug reports. Unless I missed something, I think maybe the problem is with the 'default browser behaviour' dealing with cookies and CORS -- and not with fetch (reading through the polyfill source code, it seems 100% ignorant of cookies). A few bug reports suggest a malformed server response can prevent cookies from being saved.
我的代码如下所示:
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 很难复制/粘贴它.
The actual authorization request is below. I am using cURL to get the exact request/response data, since Safari makes it hard to copy/paste it.
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"}
推荐答案
回答我自己的问题.
虽然我理解他们的动机,但我发现这是 Safari 的按预期工作"行为,这让我非常愤怒.XHR(可能是原生获取时的原生获取)根本不支持第三方 cookie 的设置.这种失败是完全透明的,因为它是由脚本上下文之外的浏览器处理的,因此基于客户端的解决方案实际上是不可能的.
I find it pretty enraging that this is a "working as intended" behaviour of Safari, though I understand their motivation. XHR (and presumably native fetch when it lands natively) does not support the setting of third-party cookies at all. This failure is completely transparent because it is handled by the browser outside of the scripting context, so client-based solutions are not really going to be possible.
您将在此处找到的一个推荐解决方案是打开一个窗口或 iframe 到 API 服务器上的 HTML 页面并在那里设置一个 cookie.此时,第 3 方 cookie 将开始工作.这很丑陋,而且不能保证 Safari 不会在某个时候堵住这个漏洞.
One recommended solution you will find here is to open a window or iframe to an HTML page on the API server and set a cookie there. At this point, 3rd party cookies will begin to work. This is pretty fugly and there is no guarantee that Safari won't at some point close that loophole.
我的解决方案基本上是重新实现一个身份验证系统,该系统执行会话 cookie 的功能.即:
My solution is to basically reimplement an authentication system that does what session-cookies do. Namely:
- 添加一个新标头,
X-Auth: [token]
,其中[token]
是一个非常小的、短暂的 JWT,其中包含您所需的信息session(理想情况下只有用户 ID——在应用程序的生命周期内不太可能发生变化的东西——但如果权限可以在会话期间更改,则绝对不是权限之类的东西); - 将
X-Auth
添加到Access-Control-Allow-Headers
; - 在登录期间,使用您需要的负载设置会话 cookie 和身份验证令牌(Safari 和非 Safari 用户都将获得 cookie 和身份验证标头);
- 在客户端,查找
X-Token
响应标头,并在任何时候将其作为X-Token
请求标头回显(您可以实现持久性通过使用本地存储——令牌过期,因此即使价值存在多年,也无法在某个时间点之后赎回); - 在服务器上,对于所有对受保护资源的请求,检查 cookie 并在存在时使用它;
- 否则(如果 cookie 不存在 - 因为 Safari 没有发送它),查找标头令牌,验证和解码令牌有效负载,使用提供的信息更新当前会话,然后生成新的身份验证令牌并将其添加到响应标头中;
- 照常进行.
- Add a new header,
X-Auth: [token]
, where[token]
is a very small, short-lived JWT containing the information you require for your session (ideally only the user id -- something that is unlikely to mutate during the lifetime of your application -- but definitely not something like permissions if permissions can be changed during the session); - Add
X-Auth
toAccess-Control-Allow-Headers
; - During sign-in, set the session cookie and the auth token with the payloads you require (both Safari and non-Safari users will get both the cookie and the auth header);
- On the client, look for the
X-Token
response header and echo it back as anX-Token
request header any time it sees it (you could achieve persistence by using local storage -- the token expires, so even if the value lives for years, it can't be redeemed after a certain point); - On the server, for all requests for protected resources, check for the cookie and use it if it exists;
- Otherwise (if the cookie is absent -- because Safari didn't send it), look for the header token, verify and decode the token payload, update the current session with the provided info and then generate a new auth token and add it to the response headers;
- Proceed as normally.
请注意,JWT(或类似的东西)旨在解决一个完全不同的问题,并且由于重放"问题,真的不应该用于会话管理(想想如果用户打开两个窗口会发生什么情况)标头状态).然而,在这种情况下,它们提供了您通常需要的短暂性和安全性.最重要的是,您应该在支持它们的浏览器上使用 cookie,使会话信息尽可能小,让您的 JWT 尽可能短,并构建您的服务器应用程序以防意外和恶意重放攻击.
Note that JWT (or anything similar) is intended to solve a completely different problem and should really never be used for session management because of the "replay" problem (think what could happen if a user had two windows open with their own header-state). In this case, however, they offer the transience and security you normally need. Bottom line is you should use cookies on browsers that support them, keep the session information as tiny as possible, keep your JWT as short-lived as possible, and build your server app to expect both accidental and malicious replay attacks.
相关文章