>请求跨域;
>是的,我正在设置凭据:true;
>服务器确实使用Set-Cookie标头进行响应;
>后续请求是从Chrome和FF发送的,带有cookie请求标头,但Safari不会;
>请求使用HTTPS(证书是自签名的,在开发域上,但似乎是常规请求的Safari接受);和
有人知道问题可能是什么吗?
我已经阅读了文档并经历了closed bug reports的许多内容.除非我错过了什么,我想问题可能是‘default browser behaviour’处理cookie和CORS – 而不是fetch(通过polyfill源代码阅读,似乎100%无知的饼干).一些错误报告表明,格式错误的服务器响应可能会阻止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"}
解决方法
虽然我了解他们的动机,但我觉得这是Safari的“按预期工作”的行为,这令人非常恼火. XHR(当它本地登陆时可能是原生取件)根本不支持第三方cookie的设置.这种失败是完全透明的,因为它是由脚本环境之外的浏览器处理的,因此基于客户端的解决方案实际上是不可能的.
您可以在此处找到一个推荐的解决方案,即在API服务器上的HTML页面上打开一个窗口或iframe,并在那里设置一个cookie.此时,第三方cookie将开始工作.这非常难看,并且无法保证Safari在某些时候不会关闭这个漏洞.
我的解决方案是基本上重新实现会话cookie执行的身份验证系统.即:
>添加一个新标头,X-Auth:[token],其中[token]是一个非常小的,短命的JWT,包含你的会话所需的信息(理想情况下只有用户id – 在这期间不太可能发生变异的东西)应用程序的生命周期 – 但如果在会话期间可以更改权限,则绝对不是权限;
>将X-Auth添加到Access-Control-Allow-Headers;
>在登录期间,使用您需要的有效负载设置会话cookie和身份验证令牌(Safari和非Safari用户都将获得cookie和auth标头);
>在客户端上,查找X-Token响应头并在它看到它时将其作为X-Token请求头回显(您可以通过使用本地存储实现持久性 – 令牌过期,因此即使值存在多年来,它不能在某一点之后兑换);
>在服务器上,对于受保护资源的所有请求,检查cookie并使用它(如果存在);
>否则(如果cookie不存在 – 因为Safari没有发送它),查找标头令牌,验证并解码令牌有效负载,使用提供的信息更新当前会话,然后生成新的身份验证令牌并添加它到响应头;
>正常进行.
请注意,JWT(或任何类似的)旨在解决一个完全不同的问题,并且由于“重放”问题,应该永远不会用于会话管理(想想如果用户打开两个带有自己的头状态的窗口会发生什么情况).但是,在这种情况下,它们提供了您通常需要的瞬间和安全性.最重要的是,你应该在支持它们的浏览器上使用cookie,保持会话信息尽可能小,保持你的JWT尽可能短,并构建你的服务器应用程序,以期待意外和恶意重播攻击.