为啥浏览器中未设置 res 标头中的 cookie?

Posted

技术标签:

【中文标题】为啥浏览器中未设置 res 标头中的 cookie?【英文标题】:Why cookie in res headers is not set in browser?为什么浏览器中未设置 res 标头中的 cookie? 【发布时间】:2021-03-10 03:23:41 【问题描述】:

我在http://localhost:4000 运行后端,在http://localhost:3000 运行前端。在服务器(Express)中,我将cors 策略设置为:

  app.use( cors(
    credentials: true,
    origin: "http://localhost:3000",
  ));

但是 cookie 没有设置。请求后

POST /graphql HTTP/1.1
Host: localhost:4000
Connection: keep-alive
Content-Length: 373
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/86.0.4240.75 Safari/537.36
content-type: application/json
Accept: */*
Origin: http://localhost:3000
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:3000/register
Accept-Encoding: gzip, deflate, br
Accept-Language: et-EE,et;q=0.9,en-US;q=0.8,en;q=0.7

我得到了正确的响应标头(?)Set-Cookie:

HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: http://localhost:3000
Vary: Origin
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8
Content-Length: 122
ETag: W/"7a-3NezKfolEhPugNDLsBCed9Xgfzk"
Set-Cookie: lirr=s%3A9JJT7_mt6W-zSWkg-DKkBmxK6O-i8bH3.1k88dMMeintjdSUMV6ig1DK5u4vgdYh4rnmtufww7nw; Path=/; Expires=Sat, 27 Nov 2021 09:31:14 GMT; HttpOnly; SameSite=Lax
Date: Fri, 27 Nov 2020 09:31:14 GMT
Connection: keep-alive
Keep-Alive: timeout=5

我尝试使用 Chrome 和 Firefox,但都没有设置 cookie。我没有看到我的设置有缺陷,标题看起来不错。我错过了什么?

加法

如果我让从后端设置 cookie(暴露为 Graphql API,通过 Graphql Playground 访问),它工作正常这样的 req 标头:

POST /graphql HTTP/1.1
Host: localhost:4000
Connection: keep-alive
Content-Length: 323
accept: */*
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36
content-type: application/json
Origin: http://localhost:4000
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:4000/graphql
Accept-Encoding: gzip, deflate, br
Accept-Language: et-EE,et;q=0.9,en-US;q=0.8,en;q=0.7

据我所知,有 3 行(如预期)不同:

Origin: http://localhost:4000
Sec-Fetch-Site: same-origin
Referer: http://localhost:4000/graphql

响应与前端的非常相似:

HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: http://localhost:3000
Vary: Origin
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8
Content-Length: 75
ETag: W/"4b-Ud8RJ2O7ZjwpTxFNSAQi92iV20w"
Set-Cookie: lirr=s%3A9CYQjFpVwG69-ojwqlk-FJWVNC02DMJP.Prt8U5%2Bcco9ktEL27JoOkAlACGKS%2Fy1NHfgugPypl00; Path=/; Expires=Sat, 27 Nov 2021 10:32:37 GMT; HttpOnly; SameSite=Lax
Date: Fri, 27 Nov 2020 10:32:37 GMT
Connection: keep-alive
Keep-Alive: timeout=5

不明白,为什么浏览器不通过前端设置cookie...

【问题讨论】:

您是如何发送请求的?您能否将代码添加到您的帖子中。 我使用URQL 将graphql 查询发送到后端服务器。我解决了实际部分(添加fetchOptions: credentials: 'include'urql's createClient),但我仍然不了解其背后的理论机制。无论是否设置凭据,标头仍然相同,即使对于预检 OPTIONS 请求... 【参考方案1】:

服务器端检查

我觉得你的设置很好。

  app.use( cors(
    credentials: true,
    origin: "http://localhost:3000",
  ));

我建议使用一些 API 工具(例如 POSTman)来测试您的代码,该工具默认启用 CORS 请求。如果它适用于 POSTman,那么您的服务器端代码没有问题。

CORS + Cookie 的浏览器问题

简单地说,CORS 请求只不过是对不同域的 XMLHttpRequest。 https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

根据以下文档, https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials

此外,此标志还用于指示何时在响应中忽略 cookies默认为假来自不同域的 XMLHttpRequest 无法为其自己的域设置 cookie 值,除非在发出请求之前将 withCredentials 设置为 true。通过将 withCredentials 设置为 true 获得的第三方 cookie 仍将遵循同源策略,因此请求脚本无法通过 document.cookie 或从响应头访问。

浏览器不会通过 CORS 请求 (XMLHttpRequest) 接受/传输 cookie,除非您启用它。

xhr.withCredentials = true;

通过浏览器控制台的示例

//from http://localhost:3000
const xhr = new XMLHttpRequest();
const url = 'http://localhost:4000/get-cookie-url';
   
xhr.open('GET', url);
xhr.withCredentials = true; // enable browser to transfer cookies
xhr.onload = function(e)
  console.log(e)
;
xhr.send();

Cookie 传输

为了说明,cookie 传输类似于握手

双方都需要发起握手。如果一方(服务器)启动而另一方(客户端)未启动,则不会发生握手。

服务器启动cookie传输时,

//Server response header
//Server will response with "Set-Cookie" header when browser requests
...
Set-Cookie: lirr=xxxx; ...
...

Client(Browser)启动cookie传输时,

//Client request header
//Client will request with "Cookie" header 
...
Cookie: lirr=xxxx; ....
...

【讨论】:

通过邮递员我得到了饼干。好主意,谢谢!接下来,您的链接给了我一些寻找“凭据”客户端的提示。从github.com/benawad/how-to-debug-cookies 我知道如何将“withCredentials”添加到我的客户端。如果我添加,cookie 会根据需要设置。但是:我没有看到标题有任何区别,即使在预检中也没有。所以实际的一半解决了,但理论上我仍然不清楚。 @w.k ,干得好,很高兴听到你整理了其中的一些内容。为了说明,cookie 传输与握手非常相似。我会用一些细节更新上面的帖子。 我想,在阅读了有关 CORS 的更多信息后,我明白了:我使用 urqlcreateClient 函数,该函数使用 JS Request() 进行 HTTP 请求。而Requestcredentials 具有默认的“同源”。如果我没有将它的客户端设置为“包含”,它只会忽略原始来源未设置的 cookie。我假设此信息包含在标头中,但它们已在客户端进行管理。再次感谢您,您的提示使我找到了解决方案。 @w.k ,太棒了? ,谢谢,您的问题对我来说也很好。自网景时代以来,Web 浏览器标准发生了很大变化。我们的工具/框架也在下面抽象。因此,我们有时会感到困惑。?

以上是关于为啥浏览器中未设置 res 标头中的 cookie?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 AJAX 请求返回后浏览器不设置 cookie?

为啥在使用 setRequestHeader 制作 xmlhttprequest 时无法设置 cookie 和 set-cookie 标头?

为啥浏览器会忽略 set-cookie 标头,并且未使用 fetch 从 Ajax 调用中保存 cookie?

为啥 Chrome 会忽略 Set-Cookie 标头?

如何将标头身份验证承载中的令牌发送到浏览器

移动谷歌浏览器不发送cookies