启用跨源资源共享(CORS)的终极指南

722 阅读9分钟

考虑以下情况:你试图使用fetch() ,从你网站的一个API中获取一些数据,但最后却出现了错误。

你打开控制台,看到 "所请求的资源上没有Access-Control-Allow-Origin 头",或者 "Access-Control-Allow-Origin 头的值<some_url> ,不等于所提供的来源",用红色的文字表示你的请求被 CORS 策略阻止了。

Blocked Request due to CORS

似乎很熟悉?在StackOverflow的cors 标签下有超过10000个问题,它是困扰前端开发者和后端开发者的最常见的问题之一。那么,到底什么是CORS策略,为什么我们经常面临这个错误?

什么是跨源资源共享(CORS)?

有趣的是,这并不是我们所描绘的错误,而是预期的行为。我们的网络浏览器强制执行同源政策,限制不同来源的资源共享。跨原点资源共享,或称 CORS,是我们可以克服这一障碍的机制。为了理解 CORS,我们首先要理解同源政策及其必要性。

同源政策

简单地说,同源政策是浏览器的 "不要和陌生人说话 "的网络版本。

今天所有的现代网络浏览器都遵循同源策略,限制来自一个来源的XMLHttpRequestfetch 请求与来自另一个来源的资源进行交互。究竟什么是原点?

它是一个方案、域名和端口的组合。方案可以是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 预检的请求属于简单请求的范畴。然而,该请求只有在被视为简单请求后才必须满足一些条件。这些条件是:。

  1. 请求的HTTP方法应该是下列之一。GET,POSTHEAD
  2. 除了用户代理自动设置的头之外,请求的头应该只包括CORS安全列表中的头,如Accept,Accept-Language,Content-Language, 和Content-Type
  3. Content-Type 标头应该只有这三个值中的一个:application/x-www-form-urlencoded,multipart/form-data, 或text/plain
  4. 如果使用XMLHttpRequest.upload 属性返回的对象,则不在该对象上注册事件监听器。XMLHttpRequest
  5. 请求中不应该使用ReadableStream 对象。

如果不能满足这些条件中的任何一个,该请求将被认为是一个预处理的请求。对于这样的请求,浏览器必须首先使用OPTIONS 方法向不同的原点发送一个请求。

这是用来检查实际的请求是否可以安全地发送至服务器。对实际请求的批准或拒绝取决于对预飞行请求的响应头信息。如果这些响应头信息与主请求的头信息不匹配,请求就不会被发出。

启用 CORS

让我们考虑一下我们最初的情况,我们面临着CORS错误。我们有多种方法可以解决这个问题,这取决于我们是否能够访问托管资源的服务器。我们可以把它缩小到两种情况。

  1. 你可以访问后端或认识后端开发者
  2. 你只能管理前端,不能访问后端服务器

如果你能访问后端。

因为 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-TypeAuthorization ,并有 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.