为跨源请求设置cookie

时间:2017-09-18 21:23:58

标签: authentication cookies cors http-headers localhost

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

以下是对我情况的解释:

我正在尝试为localhost:4000上托管的网络应用中localhost:3000上运行的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开发者工具的“网络”标签检查流量时,我可以在Response Cookies下看到Cookie。但是,我无法在Storage/Cookies下的应用程序选项卡中看到设置的cookie。我没有看到任何CORS错误,所以我认为我错过了其他的东西。

有什么建议吗?

更新I:

我正在使用React-Redux应用程序中的request模块向服务器上的/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) => {
    // doing stuff
})

更新II:

我现在设置请求和响应标头现在像疯了一样,确保它们在请求和响应中都存在。下面是截图。请注意标题Access-Control-Allow-CredentialsAccess-Control-Allow-HeadersAccess-Control-Allow-MethodsAccess-Control-Allow-Origin。看看我在Axios's github找到的问题,我的印象是现在已经设置了所有必需的标题。然而,仍然没有运气......

enter image description here

6 个答案:

答案 0 :(得分:40)

您需要做什么

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

后端(服务器): 将HTTP标头Access-Control-Allow-Credentials值设置为true。 此外,请确保已设置HTTP标头Access-Control-Allow-Origin

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

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

如果你出于某种原因不要避免它。解决方案在上面。

事实证明,如果域名包含端口,则Chrome不会设置Cookie。将其设置为localhost(没有端口)不是问题。非常感谢Erwin提示!

答案 1 :(得分:14)

2020年发布的Chrome浏览器说明。

Chrome的未来版本将仅提供跨站点的Cookie 请求是否设置了SameSite=NoneSecure

因此,如果您的后端服务器未设置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

答案 2 :(得分:3)

为了使客户端能够读取跨源请求中的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读取它,并将其写入每个请求的标头中。

所需的结果是这样的:

response request

答案 3 :(得分:1)

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

在CorsOption中:将origin设置为您的本地主机URL或前端生产URL,并将credentials设置为true 例如

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

我使用config npm module动态设置了我的原点。

然后,在res.cookie中:

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

对于生产环境,您需要将sameSite设置为none进行跨域请求,将secure设置为true。请记住,sameSite仅适用于目前的Express最新版本,而最新的Chrome版本仅在https上设置cookie,因此需要安全选项。

这就是我如何使我的动感

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

答案 4 :(得分:0)

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

Expires / Max-Age: "Session"

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

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

希望这对将来可能遇到相同问题的人很有帮助。

答案 5 :(得分:0)

  1. 前端

    `await axios.post(`your api`, data,{
        withCredentials:true,
    })
    await axios.get(`your api`,{
            withCredentials:true,
        });`
    
  2. 后端

    var  corsOptions  = {
     origin: 'http://localhost:3000', //frontend url
     credentials: true}
    
    
    app.use(cors(corsOptions));
    const token=jwt.sign({_id:user_id},process.env.JWT_SECRET,{expiresIn:"7d"});
    res.cookie("token",token,{httpOnly:true});
    
    
    
    hope it will work.