为跨源请求设置 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-Credentials
、Access-Control-Allow-Headers
、Access-Control-Allow-Methods
和Access-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:true
和 Access-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-Origin
和 Access-Control-Allow-Headers
,并且没有使用通配符 *
。
有关在 express js 中设置 CORS 的更多信息read the docs here
Cookie 设置:
2021 年每个 Chrome 和 Firefox 更新的推荐 Cookie 设置:SameSite=None
和 Secure
。见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=None
和Secure
设置的请求。
因此,如果您的后端服务器未设置 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 = none
和secure
,并且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的主要内容,如果未能解决你的问题,请参考以下文章