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

以下是对我的情况的解释:

我试图在localhost:3000上托管的web应用程序中为运行在localhost:4000上的API设置一个cookie。

似乎我在浏览器中收到了正确的响应头,但不幸的是,它们没有效果。这些是响应头:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:3000
Vary: Origin, Accept-Encoding
Set-Cookie: token=0d522ba17e130d6d19eb9c25b7ac58387b798639f81ffe75bd449afbc3cc715d6b038e426adeac3316f0511dc7fae3f7; Max-Age=86400; Domain=localhost:4000; Path=/; Expires=Tue, 19 Sep 2017 21:11:36 GMT; HttpOnly
Content-Type: application/json; charset=utf-8
Content-Length: 180
ETag: W/"b4-VNrmF4xNeHGeLrGehNZTQNwAaUQ"
Date: Mon, 18 Sep 2017 21:11:36 GMT
Connection: keep-alive

此外,当我使用Chrome开发工具的网络选项卡检查流量时,我可以在响应cookie下看到cookie。然而,我不能看到一个cookie被设置在应用程序选项卡下的存储/ cookie。我没有看到任何CORS错误,所以我假设我还遗漏了其他东西。

有什么建议吗?

我更新:

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

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) => {
    // doing stuff
})

更新二:

我现在像疯狂一样设置请求和响应头,确保它们在请求和响应中都存在。下面是截图。注意标题Access-Control-Allow-Credentials, Access-Control-Allow-Headers, Access-Control-Allow-Methods和Access-Control-Allow-Origin。看看我在Axios的github上发现的问题,我的印象是所有必需的标题现在都设置好了。然而,还是没有运气……


当前回答

为了让客户端能够从跨源请求读取cookie,您需要具备:

来自服务器的所有响应都需要在它们的头中有以下内容: Access-Control-Allow-Credentials:真 客户端需要用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和授权令牌,这些请求需要将此令牌附加到它们(在头中),以便服务器验证和执行请求。

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

期望的结果是这样的:

其他回答

为了让客户端能够从跨源请求读取cookie,您需要具备:

来自服务器的所有响应都需要在它们的头中有以下内容: Access-Control-Allow-Credentials:真 客户端需要用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和授权令牌,这些请求需要将此令牌附加到它们(在头中),以便服务器验证和执行请求。

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

期望的结果是这样的:

皮姆的回答很有帮助。在我的情况下,我必须使用

Expires / Max-Age: "Session"

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

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

希望对以后遇到同样问题的人有所帮助。

这是对上面关于Heroku服务器的CORS cookie的“Lode Michels”的回答,(以及其他云提供商,如AWS)

你的CORS cookie无法设置的原因是因为Heroku剥离了负载均衡器的SSL证书,所以当你试图在服务器上设置“安全”cookie时,它会失败,因为它不再来自安全连接。

您可以显式地指定连接是否安全,而不是cookie模块检查请求。 https://github.com/pillarjs/cookies

用可可豆,加入这个:

ctx.cookies.secure = true;

编辑:我不能直接评论这个答案,因为低于50的声誉

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

未来的Chrome版本将只提供跨站点的cookie 请求,如果他们设置为SameSite=None和安全。

所以如果你的后端服务器没有设置SameSite=None, Chrome将默认使用SameSite=Lax,不会使用这个cookie {withCredentials: true}请求。

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

Firefox和Edge的开发人员也希望在未来发布这一功能。

Spec在这里找到:https://datatracker.ietf.org/doc/html/draft-west-cookie-incrementalism-01#page-8

希望这能有所帮助 对于我关于sameSite属性,启用CORS后,我还添加了“CookieSameSite = SameSiteMode。没有一个“ 到启动文件中的CookieAuthenticationOptions

app.UseCookieAuthentication(新CookieAuthenticationOptions { … CookieSameSite = SameSiteMode。没有, … }