【问题标题】:Set cookies for cross origin requests为跨源请求设置 cookie
【发布时间】:2018-02-27 12:43:57
【问题描述】:

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

下面是我的情况的解释:

我正在尝试在localhost:3000 上托管的网络应用程序中为localhost:4000 上运行的 API 设置 cookie。

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

HTTP/1.1 200 正常 访问控制允许来源:http://localhost:3000 变化:来源,接受编码 设置 Cookie:令牌=0d522ba17e130d6d19eb9c25b7ac58387b798639f81ffe75bd449afbc3cc715d6b038e426adeac3316f0511dc7fae3f7;最大年龄=86400;域=本地主机:4000;路径=/;过期=格林威治标准时间 2017 年 9 月 19 日星期二 21:11:36; HttpOnly 内容类型:应用程序/json;字符集=utf-8 内容长度:180 ETag:W/“b4-VNrmF4xNeHGeLrGehNZTQNwAaUQ” 日期:2017 年 9 月 18 日星期一 21:11:36 GMT 连接:保持活动

此外,当我使用 Chrome 开发人员工具的“网络”选项卡检查流量时,我可以在 Response Cookies 下看到 cookie。但是,我看不到在Storage/Cookies 下的“应用程序”选项卡中设置了cookie。我没有看到任何 CORS 错误,所以我认为我遗漏了其他内容。

有什么建议吗?

更新一:

我在 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) => { // 做事 })

更新二:

我现在疯狂地设置请求和响应标头,确保它们同时出现在请求和响应中。下面是一个截图。注意标题Access-Control-Allow-CredentialsAccess-Control-Allow-HeadersAccess-Control-Allow-MethodsAccess-Control-Allow-Origin。查看我在Axios's github 发现的问题,我的印象是所有必需的标头现在都已设置。然而,仍然没有运气......

【问题讨论】:

  • @PimHeijden 看看这个:developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/… 也许你需要使用 withCredentials ?
  • 好的,你正在使用请求,我认为这不是最好的选择,看看这篇文章和答案,我认为 axios 可能对你有用。 stackoverflow.com/questions/39794895/…
  • 谢谢!我没有注意到request 模块不适用于浏览器。到目前为止,Axios 似乎做得很好。我现在收到两个标题:Access-Control-Allow-Credentials:trueAccess-Control-Allow-Origin:http://localhost:3000(用于启用 CORS)。这似乎是正确的,但 Set-Cookie 标头没有做任何事情......
  • 同样的问题,但直接使用 Axios :stackoverflow.com/q/43002444/488666。虽然 Axios 端确实需要 { withCredentials: true },但服务器标头也必须仔细检查(参见 stackoverflow.com/a/48231372/488666
  • 什么服务器头文件?

标签: authentication cookies cors http-headers localhost


【解决方案1】:

你需要做什么

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

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

有关在 express js 中设置 CORS 的更多信息read the docs here

Cookie 设置: 2021 年每个 Chrome 和 Firefox 更新的推荐 Cookie 设置:SameSite=NoneSecure。见MDN

在执行SameSite=None 时,甚至需要Secure。见MDN

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

或者

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

如果您出于任何原因,请不要避免。解决方法如上。

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

【讨论】:

  • 我认为你有这个问题只是因为localhost 在此处检查:stackoverflow.com/a/1188145,这也可能对你的情况有所帮助 (stackoverflow.com/questions/50966861/…)
  • 这个答案对我帮助很大!花了很长时间才找到它。但我认为答案应该提到将Access-Control-Allow-Origin 设置为显式域,而不仅仅是"*" 也是必需的。那么这将是完美的答案
  • 这是一个很好的答案,所有 CORS、标头、后端和前端的设置,并避免使用本地覆盖 /etc/hosts 的本地主机和真正的子域,但我仍然看到邮递员显示 SET-COOKIE在响应标头中,但 chrome 调试未在响应标头中显示此内容,并且 cookie 实际上并未在 chrome 中设置。还有其他要检查的想法吗?
  • @bjm88 你最终解决了这个问题吗?我处于完全相同的情况。从 localhost:3010 连接到 localhost:5001 时,cookie 设置正确,但从 localhost:3010 到 fakeremote:5001 (在我的主机文件中指向 127.0.0.1)不起作用。当我将服务器托管在具有自定义域的真实服务器上(从 localhost:3010 连接到 mydomain.com)时,情况完全相同。我已完成此答案中推荐的所有内容,并尝试了许多其他方法。
  • 是的,谢谢,答案就在这里。我不完全理解你的问题,但我会尽快回答。 Cookie 不尊重端口。只需从 cookie 域中省略端口即可。无论如何,端口都不是域概念的一部分。如果域确实不同,请尽可能使用代理。这样一来,您就可以完全避免 CORS 问题。
【解决方案2】:

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

Chrome 的未来版本将只提供跨站点的 cookie 使用SameSite=NoneSecure 设置的请求。

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

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

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

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

【讨论】:

  • 提供 samesite=none 和安全标志需要 HTTPS。如何在不能选择 HTTPS 的本地系统中实现这一点?我们可以绕过吗?
  • @nirmalpatel 只需删除 Chome 开发控制台中的“Lax”值即可。
  • @LennyLip 我认为如果你删除“Lax”值,它实际上仍然默认为Lax,而不是None。 (根据web.dev/samesite-cookies-explained,以及MDN。)
  • @MattBrowne 它对我有用。无论如何,在上一个 Chrome 中,我们有 EdithThisCookie 选项卡,我们可以从 SameSite 的下拉菜单中选择“无限制”值。
【解决方案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 和授权令牌,这些请求需要将此令牌附加到它们(在标头中),在命令服务器验证并执行请求。

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

想要的结果是这样的:

【讨论】:

  • 这个答案对现有答案有什么作用?
  • 一个实际的实现。我决定发布它的原因是,我花了很多时间寻找同一个问题,并将各个帖子中的部分拼凑起来以实现它。将这篇文章作为比较,对某人来说应该更容易做同样的事情。
  • @CrossOrigin 注释中显示设置allowCredentials = "true" 对我有帮助。
  • @lennylip 在上面的回答中提到,它显示相同站点和安全标志的错误。如何在没有安全标志的情况下使用 localhost 服务器实现这一点。
  • 这里只是一个提示,角度默认 csrf 拦截器实现将不起作用,使用此答案提供的拦截器将起作用,它也会在 GET 调用上附加 cookie。
【解决方案4】:

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

在 CorsOption 中:将 origin 设置为您的 localhost url 或您的前端生产 url 并将 credentials 设置为 true 例如

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

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

然后,在 res.cookie 中:

对于本地主机:您根本不需要设置sameSite 和安全选项,您可以将http cookie 的httpOnly 设置为true 以防止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,
    })

【讨论】:

    【解决方案5】:

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

    Expires / Max-Age: "Session"
    

    如果是dateTime,即使没有过期,也不会将cookie发送到后端:

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

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

    【讨论】:

      【解决方案6】:

      在尝试了你所有的建议以及更多的一天之后,我投降了。 Chrome 只是不接受我在本地主机上的跨域 cookie。 没有错误,只是默默地忽略。 我希望只有 http cookie 来更安全地存储令牌。 所以对于 localhost 来说,代理听起来是最好的解决方法。我还没有真正尝试过。

      我最终做了什么,也许它对某人有帮助。

      后端(节点/快递/打字稿)

      像往常一样设置 cookie

      res.status(200).cookie("token", token, cookieOptions)
      

      解决本地主机问题

      // if origin localhost
      response.setHeader("X-Set-Cookie", response.getHeader("set-cookie") ?? "");
      

      在 cors 中允许 x-set-cookie 标头

      app.use(cors({
          //...
          exposedHeaders: [
              "X-Set-Cookie",
              //... 
          ]
      }));
      

      前端(Axios)

      关于 Axios 的响应 删除 domain= 所以它是默认的。 拆分多个 cookie 并将它们存储在本地。

      // Localhost cookie work around
      const xcookies = response.headers?.["x-set-cookie"];
      if(xcookies !== undefined){
          xcookies
              .replace(/\s+Domain=[^=\s;]+;/g, "")
              .split(/,\s+(?=[^=\s]+=[^=\s]+)/)
              .forEach((cookie:string) => {
                  document.cookie = cookie.trim();
          });
      }
      

      不理想,但我可以重新开始我的生活。

      总的来说,我认为这只是变得复杂:-(

      更新我的用例也许我们可以解决它?

      这是一个带有自定义域的 heroku 服务器。 根据这篇文章应该没问题 https://devcenter.heroku.com/articles/cookies-and-herokuapp-com

      我做了一个孤立的测试用例,但仍然没有乐趣。 我很确定我以前在 FireFox 中看到过它,但目前似乎没有任何效果,除了我讨厌的工作。

      服务器端

      app.set("trust proxy", 1);
      
      app.get("/cors-cookie", (request: Request, response: Response) => {
      
          // http://localhost:3000
          console.log("origin", request.headers?.["origin"]);
      
          const headers = response.getHeaders();
          Object.keys(headers).forEach(x => {
              response.removeHeader(x);
              
              console.log("remove header ", x, headers[x]);
          });
          console.log("headers", response.getHeaders());
      
          const expiryOffset = 1*24*60*60*1000; // +1 day
      
          const cookieOptions:CookieOptions = {
              path: "/",
              httpOnly: true,
              sameSite: "none",
              secure: true,
              domain: "api.xxxx.nl",
              expires: new Date(Date.now() + expiryOffset)
          }
      
          return response
              .status(200)
              .header("Access-Control-Allow-Credentials", "true")
              .header("Access-Control-Allow-Origin", "http://localhost:3000")
              .header("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT")
              .header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
              .cookie("test-1", "_1_", cookieOptions)
              .cookie("test-2", "_2_", {...cookieOptions, ...{ httpOnly: false }})
              .cookie("test-3", "_3_", {...cookieOptions, ...{ domain: undefined }})
              .cookie("test-4", "_4_", {...cookieOptions, ...{ domain: undefined, httpOnly: false }})
              .cookie("test-5", "_5_", {...cookieOptions, ...{ domain: undefined, sameSite: "lax" }})
              .cookie("test-6", "_6_", {...cookieOptions, ...{ domain: undefined, httpOnly: false, sameSite: "lax" }})
              .cookie("test-7", "_7_", {...cookieOptions, ...{ domain: "localhost"}}) // Invalid domain
              .cookie("test-8", "_8_", {...cookieOptions, ...{ domain: ".localhost"}}) // Invalid domain
              .cookie("test-9", "_9_", {...cookieOptions, ...{ domain: "http://localhost:3000"}}) // Invalid domain
              .json({
                  message: "cookie"
              });
      });
      

      客户端

      const response = await axios("https://api.xxxx.nl/cors-cookie", {
          method: "get",
          withCredentials: true,
          headers: {
              "Accept": "application/json",
              "Content-Type": "application/json",                
          }
      });
      

      这会产生以下响应

      我在“网络”>“请求”>“cookies”选项卡中看到了 cookie。

      但在 Application > Storage > Cookies 和 document.cookie 下都没有 cookie。

      【讨论】:

      • 我认为您可以使用常规的“Set-Cookie”标头来完成这项工作
      • 我也想这么想,但我做不到。会发布一些细节,也许其他人可以告诉我我错过了什么?
      • cookieOptions 变量在这里至关重要。 cookie 有哪些选项?除此之外,您的浏览器设置也会产生巨大的影响。
      • @PimHeijden 我用一个孤立的案例更新了我的帖子。
      • 尝试查看网络选项卡的每个请求 cookie 子选项卡。在那里,您可以获得有关设置或未设置 cookie 的更多信息。
      【解决方案7】:
      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.
        

      【讨论】:

      • 没有任何解释的代码很少有帮助。 Stack Overflow 是关于学习的,而不是提供 sn-ps 来盲目复制和粘贴。请编辑您的问题并解释它如何回答所提出的具体问题。见How to Answer
      【解决方案8】:

      在最新的chrome标准中,如果CORS请求带cookies,则必须开启samesite = nonesecure,并且back-end域名必须开启HTTPS

      【讨论】:

      • 你不能包含来自 http 站点的 https 调用的 cookie,SameSite=None without Secure
      【解决方案9】:

      Pim's Answer 很有帮助, 但这是我经历过的一个边缘案例,

      在我的情况下,即使我已将 Access-Control-Allow-Origin 设置为 BE 中的特定来源,但在 FE 中我将其接收为 * ;这是不允许的

      问题是,其他人处理了网络服务器设置, 在那里,有一个配置来设置 Access-Control-* 标头,它覆盖了我从 BE 应用程序设置的标头

      呼.. 花了一段时间才弄明白。

      因此,如果您设置的内容与收到的内容不匹配,请同时检查您的网络服务器配置。

      【讨论】:

        【解决方案10】:

        希望这会有所帮助 对我来说,关于 sameSite 属性,在启用 CORS 后,我还添加了“CookieSameSite = SameSiteMode.None” 到启动文件中的 CookieAuthenticationOptions

        app.UseCookieAuthentication(新的 CookieAuthenticationOptions {
        ...... CookieSameSite = SameSiteMode.None, ...... }

        【讨论】:

        猜你喜欢
        • 2015-12-21
        • 2021-01-16
        • 2018-07-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-03-10
        相关资源
        最近更新 更多