调用 Express API时出现奇怪的CORS错误该怎么办?

Access to fetch at 'https://your-api.com/user/1234' from origin 'https://your-website.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
然后你可能会试着按照错误消息的提示,将请求的模式设置为 no-cors,但是对 API 的请求还是跑不通。就算你在网上搜索一番,也很难搞清楚为什么会发生这种事情,该怎么才能解决它。好消息是,有一个 Express 库可用来修复这些 CORS 错误,但在我们着手解决这些错误之前,我们是不是该看看它们到底是什么意思?为了理解这些错误,我们先来看一下 CORS 究竟是什么。
CORS 是跨域资源共享(Cross-Origin Resource Sharing)的简写,并且所有现代浏览器都支持它。这个概念有点拗口,所以我们把它拆开来细看,才能了解它到底做的是什么事情。
资源是可在特定 URL 获取的内容,如 HTML 网页、图像或 JSON API 响应。实际上,它是构成万维网的“砖块”。
一份资源的来源(origin)是协议 + 域 + 端口,例如网址 https://your-api.com:8080/user/1234 的源是 https://your-api.com:8080。如果网址不包含端口,则源就只有协议 + 域。
当 Web 浏览器需要确保网站的前端 JavaScript(来源 A)仅在另一来源(来源 B)明确允许的情况下才可以访问 B 的资源时,就要用到跨域资源共享。如果来源 B 确实给出了许可,那么资源就会跨域共享!
CORS 可以帮助阻止恶意网站访问和使用敏感数据。当你在浏览器中看到那些烦人的 CORS 错误时,实际上是你的 Web 浏览器正在尽最大努力保护你免受被识别为潜在恶意请求的威胁。
Web 浏览器决定是否允许跨域共享资源时,会在前端 JavaScript 发出的请求上设置 Origin 标头。然后,浏览器检查在资源响应上设置的 CORS 标头。它在响应上检查的标头取决于浏览器发出的请求类型,但响应至少必须将 Access-Control-Allow-Origin 标头设置为一个受许可的值,才能让 Web 浏览器对发起请求的前端 JavaScript 给出响应。
跨域请求的一个示例是,从网页上的前端 JavaScript(托管在一个域「来源 A」上)发起一个 GET 请求,到你托管在另一个域(来源 B)的 API 端点上。
> GET /user/1234 HTTP/1.1
> Host: your-api.com
> Origin: https://your-website.com
< HTTP/1.1 200 OK
< Access-Control-Allow-Origin: https://your-website.com
< Vary: Origin
< Content-Type: application/json; charset=utf-8
<
{"name":"Existing Person"}
请注意,Access-Control-Allow-Origin 响应标头的值与 Origin 响应的值是呼应的:https://your-website.com。Web 浏览器会看到这个 CORS 响应标头,并确认它具有与网页上的前端 JavaScript 共享响应内容的权限。
现在我们对 CORS 的概念和作用有了更好的了解,是时候设置一些 CORS 标头并修复你在网页上遇到的错误了。
如你在上面的示例中所看到的,对于 Web 浏览器来说,在对 API 的请求中发送 Origin 标头是很重要的,但在响应中发送所有重要的 Access-Control-* 标头的是你的 API。这些 CORS 标头会告诉 Web 浏览器是否允许将来自 API 的响应提供给前端 JavaScript。
为了帮助你解决你烦人的 CORS 错误,你需要用到 cors middleware package 这个库。
https://github.com/expressjs/cors
npm install cors
注意:在这篇博客文章中,我会链接到 GitHub(而非 npm)上的 cors 软件包,因为在撰写本文时,npm 上的这个包的文档已经过时了。
const cors = require("cors");
/**
* These options will be used to configure the cors middleware to add
* these headers to the response:
*
* Access-Control-Allow-Origin: https://your-website.com
* Vary: Origin
*/
const corsOptions = {
origin: "https://your-website.com"
};
/**
* This configures all of the following routes to use the cors middleware
* with the options defined above.
*/
app.use(cors(corsOptions));
app.get("/user/:id", (request, response) => {
response.json({ name: "Existing Person" });
});
app.get("/country/:id", (request, response) => {
response.json({ name: "Oceania" });
});
app.get("/address/:id", (request, response) => {
response.json({ street: "Gresham Lane", city: "Lakeville" });
});
/**
* These options will be used to configure the cors middleware to add
* these headers to the response:
*
* Access-Control-Allow-Origin: https://your-website.com
* Vary: Origin
*/
const corsOptions = {
origin: "https://your-website.com"
};
// This route is using the cors middleware with the options defined above.
app.get("/user/:id", cors(corsOptions), (request, response) => {
response.json({ name: "Existing Person" });
});
// This route is using the cors middleware with the options defined above.
app.get("/country/:id", cors(corsOptions), (request, response) => {
response.json({ name: "Oceania" });
});
/**
* We never want this API route to be requested from a browser,
* so we don't configure the route to use the cors middleware.
*/
app.get("/address/:id", (request, response) => {
response.json({ street: "Gresham Lane", city: "Lakeville" });
});
上面的示例是为简单的 GET 请求配置 CORS 的。对于其他许多类型的 CORS 请求,Web 浏览器将在发出实际 CORS 请求之前先发出 CORS“预检”(pre-flight)请求。这个预请求使用了 OPTIONS HTTP 方法,它可以帮助浏览器确定是否允许发出 CORS 请求。
cors 中间件提供了启用 CORS 预检的说明,并允许你配置在对预检请求的响应中发送的标头。
https://github.com/expressjs/cors#enabling-cors-pre-flight
希望本文能够帮助你全面了解 CORS,但有时我们确实很难弄清楚如何配置才能让 CORS 请求正常工作。下面列举一些有用的资源:
-
Will it CORS? 这个出色的工具会询问你要做什么,然后告诉你正确的 CORS 响应标头。
-
https://httptoolkit.tech/will-it-cors/
-
CORS HTTP headers 是一个方便的参考,列出了你可以使用的所有 CORS 标头。
-
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#CORS
-
Simple requests 和 Preflighted requests 是 Mozilla 开发网络上的 CORS 文档,它们对不同类型的 CORS 请求给出了很好的解释。
-
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Simple_requests
-
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Preflighted_requests
-
Path of an XMLHttpRequest(XHR) through CORS 这份流程图是很有用的可视工具,有助于理解何时将 CORS 请求视为“复杂”。
-
https://en.wikipedia.org/wiki/Cross-origin_resource_sharing#/media/File:Flowchart_showing_Simple_and_Preflight_XHR.svg
-
Fetch standard: HTTP extensions 这份文档涵盖了如何在浏览器 Fetch API 中实现 CORS 的详细信息。
-
https://fetch.spec.whatwg.org/#http-extensions
祝大家跨域资源共享愉快!
https://simonplend.com/how-to-fix-those-confusing-cors-errors-when-calling-your-express-api/