考虑以下情况:你试图使用fetch() ,从你网站的一个API中获取一些数据,但最后却出现了错误。
你打开控制台,看到 "所请求的资源上没有Access-Control-Allow-Origin 头",或者 "Access-Control-Allow-Origin 头的值<some_url> ,不等于所提供的来源",用红色的文字表示你的请求被 CORS 策略阻止了。
似乎很熟悉?在StackOverflow的cors 标签下有超过10000个问题,它是困扰前端开发者和后端开发者的最常见的问题之一。那么,到底什么是CORS策略,为什么我们经常面临这个错误?
什么是跨源资源共享(CORS)?
有趣的是,这并不是我们所描绘的错误,而是预期的行为。我们的网络浏览器强制执行同源政策,限制不同来源的资源共享。跨原点资源共享,或称 CORS,是我们可以克服这一障碍的机制。为了理解 CORS,我们首先要理解同源政策及其必要性。
同源政策
简单地说,同源政策是浏览器的 "不要和陌生人说话 "的网络版本。
今天所有的现代网络浏览器都遵循同源策略,限制来自一个来源的XMLHttpRequest 和fetch 请求与来自另一个来源的资源进行交互。究竟什么是原点?
它是一个方案、域名和端口的组合。方案可以是HTTP、HTTPS、FTP,或其他任何东西。同样地,端口也可以是任何有效的端口号。相同来源的请求基本上是那些方案、域和端口相匹配的请求。让我们看一下下面的例子。
假设我们的原点是 [http://localhost:3000](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) |
| blog.logrocket.com | 交叉原产地 | 不同的方案、域名和端口 |
这就是为什么你的前端运行在 [http://localhost:3000](http://localhost:3000)上运行的前端不能对你的服务器进行API调用 [http://localhost:5000](http://localhost:5000)或任何其他端口进行API调用。
另外,从原点 [https://mywebsite.com](https://mywebsite.com)到原点 [https://api.mywebsite.com](https://api.mywebsite.com)的请求仍然被认为是跨站请求,即使第二个起源是一个子域。
由于同源政策,浏览器会自动阻止跨源请求的响应与客户端共享。这对安全来说是很好的!但是,并不是所有的网站都是恶意的。但并不是所有的网站都是恶意的,有多种情况下,你可能需要从不同的源头获取数据,特别是在现代的微服务架构时代,不同的应用程序被托管在不同的源头。
这是一个很好的切入点,让我们深入了解 CORS,并学习如何使用它来允许跨源请求。
使用 CORS 允许跨站请求
我们已经确定,浏览器不允许在不同的原点之间共享资源,但有无数的例子表明,我们能够这样做。怎么做到的?这就是CORS发挥作用的地方。
CORS是一个基于HTTP头的协议,可以实现不同来源之间的资源共享。除了HTTP头之外,CORS还依赖于浏览器的preflight-flight请求,对非简单请求使用OPTIONS 方法。在本文后面会有更多关于简单和预检请求的内容。
因为 HTTP 头信息是 CORS 机制的关键,让我们看看这些头信息以及它们每个人的含义。
Access-Control-Allow-Origin
Access-Control-Allow-Origin 响应头也许是由 CORS 机制设置的最重要的 HTTP 头信息。这个头的值包括被允许访问资源的来源。如果这个标头没有出现在响应标头中,这意味着服务器上没有设置 CORS。
如果这个标头存在,它的值将与请求标头的Origin 标头进行检查。如果数值匹配,请求将被成功完成,资源将被共享。一旦不匹配,浏览器将回应一个CORS错误。
在公共API的情况下,为了允许所有原点访问资源,Access-Control-Allow-Origin 头可以在服务器上设置为* 。为了只限制特定的来源访问资源,可以将该标头设置为客户端来源的完整域,例如 [https://mywebsite.com](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-Max-Age
预飞行请求要求浏览器首先使用OPTIONS HTTP方法向服务器发出请求。只有在这之后,如果认为是安全的,才可以进行主要的请求。然而,为每个预飞行请求进行OPTIONS 调用可能很昂贵。
为了防止这种情况,服务器可以用Access-Control-Max-Age 头来响应,允许浏览器将预飞行请求的结果缓存一定时间。这个头的值是以delta秒为单位的时间量。
总的来说,这里是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.upload属性返回的对象,则不在该对象上注册事件监听器。XMLHttpRequest - 请求中不应该使用
ReadableStream对象。
如果不能满足这些条件中的任何一个,该请求将被认为是一个预处理的请求。对于这样的请求,浏览器必须首先使用OPTIONS 方法向不同的原点发送一个请求。
这是用来检查实际的请求是否可以安全地发送至服务器。对实际请求的批准或拒绝取决于对预飞行请求的响应头信息。如果这些响应头信息与主请求的头信息不匹配,请求就不会被发出。
启用 CORS
让我们考虑一下我们最初的情况,我们面临着CORS错误。我们有多种方法可以解决这个问题,这取决于我们是否能够访问托管资源的服务器。我们可以把它缩小到两种情况。
- 你可以访问后端或认识后端开发者
- 你只能管理前端,不能访问后端服务器
如果你能访问后端。
因为 CORS 只是一个基于 HTTP 标头的机制,你可以配置服务器用适当的标头来响应,以实现不同来源的资源共享。看看我们上面讨论的 CORS 标头,并相应地设置标头。
对于Node.js +Express.js开发者,你可以从npm安装cors 中间件。这里有一个使用Express网络框架的片段,以及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,它将只允许来自GET 的请求。 [https://yourwebsite.com](https://yourwebsite.com)的请求,其标题为Content-Type 和Authorization ,并有 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/](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');
结论
当我们学会欣赏同源策略对跨站伪造攻击的安全性时,CORS似乎有了很大的意义。虽然控制台中出现的红色CORS错误信息不会神奇地消失,但你现在已经具备了处理这些信息的知识,无论你是在前端还是在后端工作。
The post Theultimate guide to enable Cross-Origin Resource Sharing (CORS)appeared first onLogRocket Blog.