设置跨源请求的Cookie


102

如何跨源共享Cookie?更具体地说,如何将Set-Cookie标头与标头结合使用Access-Control-Allow-Origin

这是我的情况的解释:

我正在尝试为localhost:4000在托管的Web应用程序中运行的API设置Cookie localhost:3000

看来我在浏览器中收到了正确的响应头,但是不幸的是它们没有作用。这些是响应头:

HTTP / 1.1 200 OK
Access-Control-Allow-Origin:http:// localhost:3000
有所不同:来源,接受编码
Set-Cookie:token = 0d522ba17e130d6d19eb9c25b7ac58387b798639f81ffe75bd449afbc3cc715d6b038e426adeac3316f0511dc7fae3f7; 最大年龄= 86400; 域=本地主机:4000; 路径= /; Expires = Tue,19 Sep 2017 21:11:36 GMT; HttpOnly
内容类型:application / json; 字符集= utf-8
内容长度:180
ETag:W /“ b4-VNrmF4xNeHGeLrGehNZTQNwAaUQ”
日期:2017年9月18日星期一21:11:36 GMT
连接:保持活动状态

此外,Response Cookies当我使用Chrome开发人员工具的“网络”标签检查流量时,可以在下面看到Cookie 。但是,我看不到“应用程序”标签中的设置了Cookie Storage/Cookies。我没有看到任何CORS错误,因此我想我还缺少其他东西。

有什么建议?

更新一:

我在React-Redux应用程序中使用请求模块/signin向服务器上的端点发出请求。对于服务器,我使用express。

快递服务器:

res.cookie('token','xxx-xxx-xxx',{maxAge:86400000,httpOnly:true,domain:'localhost:3000'})

在浏览器中的请求:

request.post({uri:'/ signin',json:{userName:'userOne',password:'123456'}},(err,response,body)=> {
    //做事
})

更新二:

现在,我正在疯狂地设置请求和响应头,以确保它们同时存在于请求和响应中。以下是屏幕截图。注意头Access-Control-Allow-CredentialsAccess-Control-Allow-HeadersAccess-Control-Allow-MethodsAccess-Control-Allow-Origin。看一下我在Axios的github上发现的问题,我的印象是现在已经设置了所有必需的标头。然而,仍然没有运气...

在此处输入图片说明


4
@PimHeijden对此进行了查看:developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest / ...也许您需要withCredentials的使用?
卡拉马里科

2
好的,您正在使用请求,我认为这不是最佳选择,请看一下这篇文章和答案,我认为axios对您有用。stackoverflow.com/questions/39794895/…–
Kalamarico

谢谢!我没有注意到该request模块不适用于浏览器。到目前为止,Axios似乎做得很好。现在,我同时收到标头:Access-Control-Allow-Credentials:trueAccess-Control-Allow-Origin:http://localhost:3000(用于启用CORS)。这似乎是正确的,但Set-Cookie标头不执行任何操作……
Pim Heijden

同样的问题,但直接使用Axios:stackoverflow.com/q/43002444/488666。虽然{ withCredentials: true }是由爱可信方的确需要,服务器头必须仔细检查,以及(见stackoverflow.com/a/48231372/488666
雾ž

什么服务器头?
Pim Heijden

Answers:


169

你需要做什么

要允许通过CORS请求成功接收和发送Cookie,请执行以下操作。

后端(服务器): 将HTTP标头Access-Control-Allow-Credentials值设置为true。另外,请确保已设置HTTP标头Access-Control-Allow-OriginAccess-Control-Allow-Headers并且未使用通配符*

有关在express js中设置CORS的更多信息,请阅读此处的文档

前端(客户端):XMLHttpRequest.withCredentials标志设置为true,这可以通过不同的方式来实现,具体取决于所使用的请求-响应库:

要么

避免将CORS与Cookie结合使用。您可以使用代理来实现。

如果出于任何原因,不要回避它。解决方案在上面。

事实证明,如果域包含端口,Chrome不会设置cookie。将其设置为localhost(无端口)不是问题。非常感谢Erwin提供的提示!


2
我认为你有这个问题,只是因为的localhost检查在这里这样的:stackoverflow.com/a/1188145,也可能帮助你的情况下(stackoverflow.com/questions/50966861/...
埃德温·

5
这个答案对我有很大帮助!花了很长时间才找到它。但是我认为答案应该提到设置Access-Control-Allow-Origin为显式域,而不仅仅是设置"*"。那么这将是一个完美的答案
e.dan

6
这是一个很好的答案,并且为CORS,标头,后端和前端进行了所有设置,并避免了本地主机使用真实子域在本地覆盖/ etc / hosts,但我仍然看到邮递员在响应标头中显示了SET-COOKIE,但是chrome debug却没有在响应标头中显示此信息,并且cookie实际上未在chrome中设置。还有其他想法要检查吗?
bjm88 '19

1
@ bjm88您最终弄明白了吗?我的情况完全一样。从localhost:3010连接到localhost:5001时,cookie设置正确,但从localhost:3010到fakeremote:5001(在我的主机文件中指向127.0.0.1)不起作用。当我将服务器托管在具有自定义域的真实服务器上(从localhost:3010连接到mydomain.com)时,情况完全相同。我已经完成了此答案中推荐的所有操作,并且尝试了许多其他操作。
表格

1
在棱角分明,所需的客户端的变化,也是增加withCredentials:真正传递给HttpClient.post,不用彷徨的选项,选项等
马文

17

2020年发布的Chrome浏览器的注意事项。

如果将来的Chrome浏览器版本将Cookie设置为SameSite=None和,则只会提供带有跨站点请求的Cookie Secure

因此,如果您的后端服务器未设置SameSite = None,则Chrome将默认使用SameSite = Lax,并且不会将此{cookie withCredentials:true}请求使用此cookie。

更多信息https://www.chromium.org/updates/same-site

Firefox和Edge开发人员也希望将来发布此功能。

在此处找到规格:https : //tools.ietf.org/html/draft-west-cookie-incrementalism-01#page-8


4
提供samesite = none并且安全标志需要HTTPS。如何在不能使用HTTPS的本地系统中实现此目标?我们可以绕开吗?
nirmal patel

@nirmalpatel只需在Chome开发控制台中删除“ Lax”值即可。
LennyLip

4

为了使客户端能够从跨域请求中读取Cookie,您需要具备以下条件:

  1. 来自服务器的所有响应都必须在标头中包含以下内容:

    Access-Control-Allow-Credentials: true

  2. 客户端需要发送带有withCredentials: true选项的所有请求

在使用Angular 7和Spring Boot的实现中,我通过以下方式实现了这一目标:


服务器端:

@CrossOrigin(origins = "http://my-cross-origin-url.com", allowCredentials = "true")
@Controller
@RequestMapping(path = "/something")
public class SomethingController {
  ...
}

origins = "http://my-cross-origin-url.com"部分将添加Access-Control-Allow-Origin: http://my-cross-origin-url.com到每个服务器的响应头中

allowCredentials = "true"部分将添加Access-Control-Allow-Credentials: true到每个服务器的响应头中,这是我们为了使客户端读取cookie所需要的


客户端:

import { HttpInterceptor, HttpXsrfTokenExtractor, HttpRequest, HttpHandler, HttpEvent } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from 'rxjs';

@Injectable()
export class CustomHttpInterceptor implements HttpInterceptor {

    constructor(private tokenExtractor: HttpXsrfTokenExtractor) {
    }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        // send request with credential options in order to be able to read cross-origin cookies
        req = req.clone({ withCredentials: true });

        // return XSRF-TOKEN in each request's header (anti-CSRF security)
        const headerName = 'X-XSRF-TOKEN';
        let token = this.tokenExtractor.getToken() as string;
        if (token !== null && !req.headers.has(headerName)) {
            req = req.clone({ headers: req.headers.set(headerName, token) });
        }
        return next.handle(req);
    }
}

使用此类,您实际上可以为所有请求注入其他内容。

第一部分req = req.clone({ withCredentials: true });是发送带有withCredentials: true选项的每个请求所需要的。实际上,这意味着将先发送一个OPTION请求,以便您在发送实际的POST / PUT / DELETE请求之前,在其中获取cookie和授权令牌,而在实际的POST / PUT / DELETE请求中,需要将此令牌附加到它们(在标头中),命令服务器验证并执行请求。

第二部分是专门处理所有请求的反CSRF令牌的部分。在需要时从cookie读取它,并将其写入每个请求的标头中。

期望的结果是这样的:

响应 请求


这个答案对现有答案有什么帮助?
Pim Heijden

2
实际的实现。我之所以决定发布它,是因为我花了很多时间来寻找相同的问题,然后将各个帖子中的内容加在一起以实现它。以这篇文章为比较,这样做应该更容易一些。
Stefanos Kargas

allowCredentials = "true"@CrossOrigin注释中显示设置对我有帮助。
仔细查看

@lennylip在上面的回答中提到,它显示相同站点和安全标志的错误。如何使用没有安全标志的localhost服务器实现该目标。
nirmal patel

2

对于Express,请将您的Express库升级到4.17.1最新的稳定版本。然后;

在CorsOption:设置origin到localhost网址或前端生产的网址和credentialstrue

  const corsOptions = {
    origin: config.get("origin"),
    credentials: true,
  };

我使用config npm module动态设置我的来源。

然后,在res.cookie中:

对于localhost:完全不需要设置sameSite和secure选项,可以将http cookie设置httpOnlytrue,以防止XSS攻击和其他有用的选项,具体取决于您的用例。

对于生产环境,您需要设置sameSitenone了跨域请求,并securetrue。请记住sameSite,目前仅适用于Express最新版本,而最新的Chrome版本仅将cookie设置为over https,因此需要安全选项。

这就是我如何使我的动态

 res
    .cookie("access_token", token, {
      httpOnly: true,
      sameSite: app.get("env") === "development" ? true : "none",
      secure: app.get("env") === "development" ? false : true,
    })

0

Pim的答案非常有帮助。就我而言,我必须使用

Expires / Max-Age: "Session"

如果它是一个dateTime,即使它没有过期,它仍然不会将cookie发送到后端:

Expires / Max-Age: "Thu, 21 May 2020 09:00:34 GMT"

希望它对将来可能遇到相同问题的人们有所帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.