为跨源请求设置 cookie

Posted

技术标签:

【中文标题】为跨源请求设置 cookie【英文标题】:Set cookies for cross origin requests 【发布时间】: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 可能对你有用。 ***.com/questions/39794895/… 谢谢!我没有注意到request 模块不适用于浏览器。到目前为止,Axios 似乎做得很好。我现在收到两个标题:Access-Control-Allow-Credentials:trueAccess-Control-Allow-Origin:http://localhost:3000(用于启用 CORS)。这似乎是正确的,但 Set-Cookie 标头没有做任何事情...... 同样的问题,但直接使用 Axios :***.com/q/43002444/488666。虽然 Axios 端确实需要 withCredentials: true ,但服务器标头也必须仔细检查(参见 ***.com/a/48231372/488666) 什么服务器头文件? 【参考方案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,这可以根据所使用的请求-响应库以不同的方式实现:

jQuery 1.5.1xhrFields: withCredentials: true

ES6 fetch()credentials: 'include'

axios:withCredentials: true

或者

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

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

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

【讨论】:

我认为你有这个问题只是因为localhost 在此处检查:***.com/a/1188145,这也可能对你的情况有所帮助 (***.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,您需要:

    来自服务器的所有响应都需要在其标头中包含以下内容:

    Access-Control-Allow-Credentials: true

    客户端需要使用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】:

    前端

    `await axios.post(`your api`, data,
        withCredentials:true,
    )
    await axios.get(`your api`,
            withCredentials:true,
        );`
    

    后台

    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, ......

【讨论】:

这并没有提供问题的答案。一旦你有足够的reputation,你就可以comment on any post;相反,provide answers that don't require clarification from the asker。 - From Review

以上是关于为跨源请求设置 cookie的主要内容,如果未能解决你的问题,请参考以下文章

浏览器没有为跨源请求设置源头?

Cookie不会在跨源请求中发送

ajax跨域请求无法携带cookie的问题

无法在 Django 网站中发布跨源请求

跨域 WebAPI 批量请求

网络知识补习❄️| 由浅入深了解HTTP跨源资源共享(CORS)