考虑以下情况:您正在尝试使用 fetch()
从网站上的 API 获取一些数据,但最终出现了错误。
您打开控制台并看到红色文本中写着“请求资源未包含Access-Control-Allow-Origin标头”或者“Access-Control-Allow-Origin标头的值 <some_url>
不等于提供的来源”,这表明您的请求被CORS策略阻止。
听起来很熟悉吗?在StackOverflow上发布了超过10,000个带有cors
标签的问题,它是困扰前端和后端开发人员最常见的问题之一。
那么,什么是CORS策略,为什么我们经常遇到此错误呢?
什么是跨源资源共享 (CORS)?
有趣的是,这并不是我们描述的错误,而是预期的行为。
我们的网络浏览器强制执行同源策略,该策略限制不同源之间的资源共享。 跨源资源共享 或 CORS 是我们克服这一障碍的机制。 要了解 CORS,首先让我们了解同源策略及其需求。
同源策略
简单来说,同源策略是浏览器实现的“不与陌生人交谈”的网络版。
今天所有现代的网络浏览器都遵循同源策略,该策略限制了一个来源的XMLHttpRequest和fetch请求如何与另一个来源的资源进行交互。那么什么是“来源”呢?
它由方案、域名和端口组成。方案可以是HTTP、HTTPS、FTP或其他任何协议。类似地,端口也可以是任何有效的端口号。同源请求基本上就是这些请求中方案、域名和端口匹配的请求。让我们看下面这个例子。
假设我们的来源为http://localhost:3000,则请求可分为同源或跨域请求:
源 | 请求类型 | 原因 |
---|---|---|
http://localhost:3000/about | 同源 | 路径“/about”不被视为来源的一部分 |
http://localhost:3000/shop/product.html | 同源 | 路径“/shop/product.html”不被视为来源的一部分 |
http://localhost:5000 | 跨域 | 不同的端口(5000 而不是 3000) |
https://localhost:3000 | 跨域 | 不同的方案(HTTPS 而不是 HTTP) |
https://www.shejibiji.com | 跨域 | 不同的方案、域和端口 |
这就是为什么在开发单页应用程序 (SPA) 时,运行在 http://localhost:3000
上的前端无法对运行 http://localhost:5000
的服务器或任何其他端口进行 API 调用的原因。
此外,从来源 “https://mywebsite.com” 到来源 “https://api.mywebsite.com” 的请求仍然被视为跨站点请求,即使第二个来源是子域名。
由于同源策略,浏览器会自动阻止跨源请求的响应与客户端共享。 出于安全原因,这很棒!
但并非所有网站都是恶意的,在多种情况下您可能需要从不同来源获取数据,尤其是在现代微服务架构,其中不同的应用程序托管在不同的源上。
对于我们深入研究 CORS 并学习如何使用它来允许跨源请求来说,这是一个很好的转折点。
通过 CORS 解决跨域问题
我们已经确定浏览器不允许不同来源之间的资源共享,但是有无数的例子让我们能够这样做。 该怎么做呢? 这就是 CORS 发挥作用的时候了。
CORS 是一种基于 HTTP 标头的协议,可实现不同来源之间的资源共享。
除了 HTTP 标头,CORS 还依赖浏览器的预检请求,使用“OPTIONS”方法处理非简单请求。
本文后面将详细介绍简单请求和预检请求。
因为 HTTP 标头是 CORS 机制的关键,让我们看看这些标头以及它们各自的含义。
Access-Control-Allow-Origin
Access-Control-Allow-Origin
响应标头可能是 CORS 机制设置的最重要的 HTTP 标头。 此标头的值由允许访问资源的来源组成。 如果响应标头中不存在此标头,则表示服务器上尚未设置 CORS。
如果存在此标头,则会根据请求标头的“Origin”标头检查其值。 如果值匹配,则请求将成功完成并共享资源。
不匹配时,浏览器将响应 CORS 错误。
在公共 API 的情况下,要允许所有来源访问资源,可以在服务器上将“Access-Control-Allow-Origin
”标头设置为“*
”。 为了限制只有特定来源访问资源,可以将标头设置为客户端来源的完整域,例如 https://mywebsite.com 。
Access-Control-Allow-Methods
Access-Control-Allow-Methods
响应头用于指定允许的HTTP方法或HTTP方法列表,例如 GET
、POST
和 PUT
,服务器可以响应这些方法。
该头在预检请求的响应中存在。如果您的请求的HTTP方法不在允许方法列表中,将导致CORS错误。 当您想要限制用户通过“POST
”、“PUT
”、“PATCH
”或“DELETE
”请求修改数据时,这非常有用。
Access-Control-Allow-Headers
“Access-Control-Allow-Headers
”响应头指示请求可以具有的允许HTTP标头的列表。为支持自定义标头,例如“x-auth-token
”,您可以相应地在服务器上设置CORS。
除了允许的标头之外的其他标头构成的请求将导致CORS错误。与“Access-Control-Allow-Methods
”标头类似,“Access-Control-Allow-Headers
”标头用于响应预检请求。
Access-Control-Max-Age
预检请求要求浏览器首先使用“OPTIONS
” HTTP方法向服务器发出请求。只有在被视为安全的情况下才能发出主请求。但是,为每个预检请求进行“OPTIONS
”调用可能是昂贵的。
为了防止这种情况,服务器可以使用“Access-Control-Max-Age
”标头进行响应,允许浏览器将预检请求的结果缓存一段时间。该标头的值是以增量秒为单位的时间量。
总体而言,以下是CORS响应头的语法:
Access-Control-Allow-Origin: <allowed_origin> | *
Access-Control-Allow-Methods: <method> | [<method>]
Access-Control-Allow-Headers: <header> | [<header>]
Access-Control-Max-Age: <delta-seconds>
简单请求与预检请求
不触发CORS预检的请求属于简单请求。但是,在被视为简单请求之后,请求必须满足一些条件。这些条件是:
- 请求的HTTP方法应为以下之一:“
GET
”,“POST
”或“HEAD
”。 - 请求标头应仅由CORS安全列出的标头组成,例如“
Accept
”,“Accept-Language
”,“Content-Language
”和“Content-Type
”,除了用户代理自动设置的标头。 - “
Content-Type
”标头应仅具有以下三个值之一:“application/x-www-form-urlencoded
”,“multipart/form-data
”或“text/plain
”。 - 如果使用“
XMLHttpRequest
”,则不应在由“XMLHttpRequest.upload
”属性返回的对象上注册事件侦听器。 - 请求中不应使用“
ReadableStream
”对象。
如果未能满足这些条件中的任何一个,则请求被视为预检请求。对于这种请求,浏览器必须首先使用“OPTIONS”方法向不同的源发送请求。
这用于检查实际请求是否安全可发送到服务器。实际请求的批准或拒绝取决于预检请求的响应标头。如果这些响应标头与主请求的标头不匹配,则不会发出请求。
启用CORS
让我们考虑我们面临CORS错误的初始情况。
我们可以根据我们是否可以访问托管资源的服务器来解决此问题。我们可以将其缩小为两种情况:
- 您可以访问后端或了解后端开发人员。
- 您只能管理前端,并且无法访问后端服务器。
如果您可以访问后端:
因为CORS只是基于HTTP头的机制,所以您可以配置服务器以响应适当的标头,以便在不同的来源之间启用资源共享。查看上面讨论的CORS标头,并相应地设置标头。
对于Node.js + Express.js开发人员,可以从npm安装cors
中间件。这是一个使用Express Web框架和CORS中间件的代码片段:
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
app.get('/', (req, res) => {
res.send('API running with CORS enabled');
});
app.listen(5000, console.log('Server running on port 5000'));
如果不传递由 CORS 配置组成的对象,将使用默认配置,相当于:
{
"origin": "*",
"methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
"preflightContinue": false,
"optionsSuccessStatus": 204
}
以下是如何在服务器上配置 CORS,它只允许来自 https://yourwebsite.com 的带有标头 Content-Type
和 Authorization
的 GET
请求,预检缓存时间为 10 分钟:
app.use(cors({
origin: 'https://yourwebsite.com',
methods: ['GET'],
allowedHeaders: ['Content-Type', 'Authorization'],
maxAge: 600
}));
虽然此代码特定于 Express.js 和 Node.js,但概念保持不变。 使用您选择的编程语言和框架,您可以使用您的响应手动设置 CORS 标头或为其创建自定义中间件。
如果你只能管理前端:
很多时候,我们可能无法访问后端服务器。 例如,公共 API。 因此,我们无法将标头添加到我们收到的响应中。 但是,我们可以使用代理服务器将 CORS 标头添加到代理请求中。
cors-anywhere 项目是一个 Node.js 反向代理,可以让我们做同样的事情。 代理服务器在 https://cors-anywhere.herokuapp.com/ 上可用,但您可以通过克隆存储库并将其部署在 等免费平台上来构建自己的代理服务器 Heroku 或任何其他所需的平台。
在这种方法中,不是像这样直接向服务器发出请求:
fetch('https://jsonplaceholder.typicode.com/posts');
只需将代理服务器的 URL 附加到 API URL 的开头,如下所示:
fetch('https://cors-anywhere.herokuapp.com/https://jsonplaceholder.typicode.com/posts');
总结 Conclusion
随着我们了解到同源策略在防范跨站请求伪造攻击方面的安全性,CORS 似乎变得更加合理。虽然控制台中红色 CORS 错误消息的发生并不会神奇地消失,但是您现在已经掌握了解决这些消息的知识,无论您是在前端还是后端工作。
翻译自:The ultimate guide to enabling Cross-Origin Resource Sharing (CORS)