16

我在具有基于会话的授权的不同域上同时使用前端和后端应用程序。我已经设置了一个有效的 CORS 配置,它可以按预期工作localhost(例如从端口:9000到端口:8080)。一旦我在安全域上部署应用程序(两个域都只允许 HTTPS),CSRF cookie 就无法再在 JavaScript 中访问,导致前端的后续请求不正确(缺少 CSRF 标头)。

cookie 由后端在Set-Cookie标头中设置,而不使用HttpOnly标志。它实际上是在浏览器的某个地方设置的,因为后续请求中同时包含会话 cookie 和 CSRF cookie。尝试通过 JavaScript 访问它(例如document.cookie在控制台中使用)会返回一个空字符串。Chrome 的 DevTools 不会在前端域上显示任何cookie(甚至没有列出后端域)。

我期望 cookie 被设置并在当前域(前端域)上可见。我正在使用axios库的withCredentials标志。

您知道为什么无法从 JavaScript 或 Chrome 中的 DevTools 访问 cookie 吗?这和Strict-Transport-Security标题有关系吗?


标头

1. 初始 GET 响应头

HTTP/1.1 401 Unauthorized
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://[my-frontend-domain]
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Encoding: gzip
Content-Type: application/json;charset=UTF-8
Date: Wed, 20 Sep 2017 11:57:07 GMT
Expires: 0
Pragma: no-cache
Server: Apache-Coyote/1.1
Set-Cookie: CSRF-TOKEN=[some-token]; Path=/
Vary: Origin,Accept-Encoding
X-Content-Type-Options: nosniff
X-Vcap-Request-Id: [some-token]
X-Xss-Protection: 1; mode=block
Content-Length: [some-length]
Strict-Transport-Security: max-age=15768000; includeSubDomains

2.后续POST请求头

POST /api/authentication HTTP/1.1
Host: [my-backend-host]
Connection: keep-alive
Content-Length: [some-length]
Pragma: no-cache
Cache-Control: no-cache
Accept: application/json, text/plain, */*
Origin: [my-frontend-host]
User-Agent: [Google-Chrome-User-Agent]
Content-Type: application/x-www-form-urlencoded
DNT: 1
Referer: [my-frontend-host]
Accept-Encoding: gzip, deflate, br
Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4,de-CH;q=0.2,it;q=0.2
Cookie: [some-other-cookies]; CSRF-TOKEN=[same-token-as-in-the-previous-request]

此请求应包含一个 CSRF 标头,如果 cookie 可通过 JavaScript 访问,则会自动添加该标头。

4

4 回答 4

10

TL;DR : 无法对跨域 cookie 进行读取访问。将 CSRF 令牌添加到响应标头将是一种解决方案。另一种完全规避 CORS 和跨域请求的解决方案是使用反向代理。


问题

正如我在上面的问题中所述,我前端的 JavaScript 部分(例如https://example1.com,正在尝试HttpOnly从我的后端访问非 cookie,例如https://example2.com。为了能够使用 JavaScript 访问远程 API,我正在使用 CORS。这允许请求通过。我withCredentials: true在前端和后端使用Access-Control-Allow-Credentials: true。然后Set-Cookie标题将cookie设置在后端原点而不是前端原点。因此, cookie 在 DevTools 和document.cookieJavaScript 的命令中都不可见。

在后端源上设置的 Cookie 始终是通过 CORS 向后端发出的请求的一部分。但是,我需要访问 CSRF cookie 的内容才能将令牌添加到请求标头中(以防止 CSRF 攻击)。正如我发现的那样,无论使用什么 CORS 设置,都无法使用 JavaScript 从不同的域读取(或写入)cookie(请参阅这些 StackOverflow 答案:[1][2])。浏览器将对 cookie 内容的访问限制在同域来源。

解决方案

由此得出结论,即不可能访问HttpOnly不同域的非 cookie 的内容。此问题的解决方法是将 CSRF 令牌设置为附加的自定义响应标头。这些标头通常也不能被不同的域访问。但是,它们可以通过后端的 CORS 设置公开Access-Control-Expose-Headers。这是安全的,只要使用严格限制的Access-Control-Allow-Origin标头即可。

另一种解决方法是使用反向代理,它完全规避了 CORS 和跨域请求的问题。使用这样的反向代理在前端提供了一个特殊的路径,该路径将被重定向到后端(服务器端)。例如,调用https://front-end/api被代理到https://back-end/api. 因为来自前端的所有请求都是在同一个域中向前端代理发出的,所以浏览器将每个调用都视为同域请求,并且 cookie 直接设置在前端源上。该解决方案的缺点包括潜在的性能问题,因为另一台服务器介于两者之间(延迟),并且 cookie 需要在两个来源上设置(直接访问后端时登录两次)。设置反向代理可以使用 nginx、apache 或http-proxy-middleware在 Node.js 中使用也很容易:

var express = require('express');
var proxy = require('http-proxy-middleware');

var options = {
	target: 'https://[server]',
	changeOrigin: true,
	secure: true
};

var exampleProxy = proxy(options);
var app = express();

app.use('/api', exampleProxy);
app.use(express.static(__dirname + "/public"));
app.listen(process.env.PORT || 8080);

于 2017-09-28T08:35:06.923 回答
6

简而言之,无法访问跨域cookies,document.cookie只能访问当前(或父)域cookies。

作为根本原因的提示是 ssc-hrep3 在他的问题中提到“两个域”。

从 localhost 部署(后端和前端服务器仅使用不同的端口)切换到使用两个不同主机的部署时,很容易犯这个错误。这将在本地工作,因为 cookie 跨端口共享,并且在使用两个不同的主机时会失败。(与其他一些也会在本地暴露的 CORS 问题不同)

有关更多信息和解决方法,请参阅ssc-hrep3 的答案

于 2017-09-30T04:02:15.787 回答
4

1

您可能需要添加Access-Control-Allow-Headers标头以允许传递特定标头。

请尝试将以下内容添加到您的服务器响应标头(OPTIONS 方法)中以进行测试

Access-Control-Allow-Headers: Content-Type, *

在生产中,我建议将标题限制如下(但我不能 100% 确定正确的标题列表,如果可行,需要在这里进行试验)

Access-Control-Allow-Headers: Cookie, Set-Cookie

请参阅此参考https://quickleft.com/blog/cookies-with-my-cors/

2

您可能遇到的另一个问题是您的cookie 将设置在您的后端服务所在的域上(而不是在您查询的域上)

请也检查一下

3

作为最后一个问题的一个选项 - 浏览器可以禁止为b.xxx.com来自请求的域设置 cookiea.xxx.com

在这种情况下,您可以尝试在父域上设置 cookie xxx.com,以便您的客户端可以使用它。

于 2017-09-27T13:03:11.320 回答
1

如您在此处阅读的,XHR 规范明确禁止读取 Set-Cookie。最好的方法是在标头而不是 cookie 中传递信息。

于 2017-09-26T02:23:36.157 回答