图解 CORS

每个开发人员偶尔看到控制台上大红色的Access to fetched has been blocked by CORS policy(CORS策略已阻止访问。。。)错误时,都会感到沮丧!😬 虽然有一些方法可以快速消除这个错误,但今天我们就别把任何事情都当作是理所当然,我们就好好看看 CORS 到底在做什么,以及为什么它实际上是我们的朋友。👏🏼

❗️ 在本文中我不会解释 HTTP 基础知识。不过,如果想更详细了解 HTTP 请求和响应的信息,我之前为此写过一篇一篇小博客。🙂 在我的例子中,我用的是 HTTP/1.1 而不是 HTTP/2,但这并不影响 CORS。


在前端,我们经常想要显示放在其他地方的数据!在显示这些数据之前,浏览器首先必须向服务器发出请求,才能获取这些数据!客户机发送一个 HTTP 请求,其中包含服务器将数据发送回客户机所需的所有信息。🙂

假设我们在 www.mywebsite.com 网站上试图获取一些来自 api.website.com 的服务器上的用户信息!

完美!😃 我们刚刚向服务器发送了一个 HTTP 请求,然后服务器发送回的响应带有我们请求的 JSON 数据。

下面我们试试完全相同的请求,但是是从另一个域发起的。现在是从 www.anotherdomain.com 发起请求,而不是从 www.mywebsite.com 发起的。

等等,什么?我们发送了完全相同的请求,但这次浏览器给我们显示了一个奇怪的错误?

实际上,我们刚刚看到的是 CORS 在其作用!💪🏼 下面我们看看为什么会出现这个错误,以及它的确切含义。


✋🏼 同源策略

Web 强制执行所谓的同源策略(Same-origin policy)。默认情况下,我们只能访问与我们的请求源位于同一来源的资源!💪🏼 比如,加载位于 https://mywebsite.com/image1.png 的一副图像是完全没问题的。

当一个资源位于不同的(子)域、协议或端口上时,就是跨源的(Cross-origin)!

image3

很酷,但是到底为什么有同源策略的存在呢?

假设没有这个同源策略,而你不小心点击了你阿姨在 Facebook 上发给你的一个病毒链接。这个链接把你重定向到一个“邪恶的网站”,这个网站有一个嵌入的 iframe,它可以加载你的银行网站,并通过一些设置好的 cookies 成功地让你登录银行网站!😬

这个“邪恶网站”的开发者让网站能够访问这个 iframe,并与你银行网站的 DOM 内容进行交互,以便代表你向他们的帐户汇款!

是啊…这是个巨大的安全风险!我们不想任何人都能访问所有东西! 😧

好在同源策略帮助我们避免这种情况!这种策略确保我们只能访问来自同一来源的资源。

在本例中,源 www.evilwebsite.com 试图访问来自 www.bank.com 的跨源资源!同源策略阻止了这种情况的发生,确保邪恶网站的开发者不能访问我们的银行数据。🥳

好吧,那…这跟CORS有啥关系呢?


🔥 客户端 CORS

虽然同源策略实际上只适用于脚本,但浏览器对 JavaScript 请求“扩展”了这个策略:默认情况下,我们只能访问从同一来源获取的资源!

嗯,但是。。。我们经常需要访问跨源的资源🤔。也许我们的前端为了加载数据,需要与后端 API 进行交互呢?为安全地允许跨源请求,浏览器用了一个叫做 CORS 的机制!🥳

CORS 代表跨源资源共享(Cross-Origin Resource Sharing)。尽管浏览器不允许我们访问不在同一来源上的资源,但是我们可以用 CORS 稍微改变一下这些安全限制,同时仍然确保我们能安全地访问这些资源🎉。

用户代理(比如浏览器)可以根据 HTTP 响应中针对 CORS 的标头的值,使用 CORS 机制来允许跨源请求,否则浏览器就会阻止这些请求!✅

当发出跨源请求时,客户端会自动给 HTTP 请求添加一个额外的标头:originOrigin 标头的值就是请求的来源!

为让浏览器允许访问跨源资源,它需要来自服务器的响应的某些标头,这些标头指定此服务器是否允许跨源请求!


💻 服务器端 CORS

作为服务器开发人员,我们可以通过在 HTTP 响应中添加额外的标头,来确保允许跨源请求,这些标头都以 Access Control-* 开头🔥。基于这些 CORS 响应头的值,浏览器现在可以允许某些跨源响应,而之前这些响应通常会被同源策略所阻止!

尽管我们可以用的 CORS 标头有好几个,但是浏览器为允许跨源资源访问只需要一个标头:Access-Control-Allow-Origin!🙂

这个标头的值指定允许哪些源访问从服务器请求的资源

如果 https://mywebsite.com 对我们在开发的服务器应该有访问权限,那我们就把该域名的值添加到 Access-Control-Allow-Origin 标头中!

太棒了!🎉 这个标头现在被添加到服务器发送回客户端的响应中。通过添加这个标头,如果我们从 https://mywebsite.com 发送请求,同一策略源将不再限制我们接收位于 https://api.mywebsite.com 源上的资源。

浏览器中的 CORS 机制检查 Access-Control-Allow-Origin 标头的值是否等于 HTTP 请求所发送的 Origin 的值🤚🏼。

在本例中,请求的来源是 https://www.mywebsite.com,它列在 Access-Control-Allow-Origin 响应头中!

完美!🎉 我们能够成功地接收到跨源资源了!那么,当我们试图从一个未在 Access-Control-Allow-Origin 标头中列出的源访问这些资源时,会发生什么呢?🤔

啊,是的,CORS 抛出了不时会让人沮丧的臭名昭著的错误!不过现在我们发现这是完全有道理的。

1
The 'Access-Control-Allow-Origin' header has a value 'https://www.mywebsite.com' that is not equal to the supplied origin. 

在本例中,提供的来源是 https://www.anotherwebsite.com。但是,服务器在 Access-Control-Allow-Origin 标头中的允许源列表中没有这个提供的源!CORS 成功地阻止了请求,我们在代码中无法访问获取的数据😃。

CORS 还允许我们添加通配符 * 作为允许的源的值。这意味着来自所有来源的请求都应该可以访问请求的资源,所以要小心!


Access-Control-Allow-Origin 是我们可以提供的众多 CORS 标头之一。服务器开发人员可以扩展服务器的 CORS 策略,以便(dis)允许以及不允许某些请求!💪🏼

另一个常见的标头是 Access-Control-Allow-Methods!通过设置这个标头,CORS 会只允许使用列出的方法发送跨源请求。

在这本例中,只允许使用 GETPOSTPUT方法的请求!PATCHDELETE 等其他方法将被阻止❌。

如果想知道其他可能的 CORS 标头是什么以及它们的用途,请查看此列表

说到 PUTPATCHDELETE 请求,其实 CORS 处理这些请求的方式有所不同!🙃 这些非简单请求启动了一个叫做预检请求的玩意儿!


🚀 预检请求

CORS 有两种类型的请求:简单请求预检请求。一个请求是简单请求,还是预检请求,取决于请求中的一些值(别担心,你不必记住这个,哈哈)。

当一个请求是 GETPOST 方法,并且没有任何自定义标头时,该请求就是简单请求!任何其他请求,比如带 PUTPATCHDELETE 方法的请求,都是预检请求。

如果只是想知道为了一个请求要成为简单请求,必须满足哪些需求,MDN 上有一个有用的列表

好吧,不过“预检请求”到底是啥意思呢?为什么会发生这种情况呢?


在发送实际请求之前,客户机会生成一个预检请求!预检请求在其 Access-Control-Request-* 标头中包含有关我们将要执行的实际请求的信息🔥。

这会向服务器提供有关浏览器试图发出的实际请求的信息:请求的方法是什么,附加标头是什么,等等。

服务器接收这个预检请求,并发送回一个空的带有服务器的 CORS 标头的 HTTP 响应!浏览器接收预检响应(这个响应除了 CORS 标头外,不包含任何数据),并检查该 HTTP 请求是否被允许!✅

如果是这样,浏览器就会将实际请求发送到服务器,然后服务器用我们请求的数据进行响应!

但是,如果不是这样的话,CORS 就会阻止预检请求,而实际的请求永远不会被发送✋🏼。预检请求是一种很好的方法,可以防止我们访问或修改服务器上尚未启用任何 CORS 策略的资源!服务器现在就可以防止潜在的不想要的跨源请求😃。

💡 为减少到服务器的往返次数,我们可以通过给 CORS 请求添加一个 Access-Control-Max-Age 标头,来缓存预检响应!浏览器可以使用通过这种方式缓存的预检响应,而不是发送一个新的预检请求!


🍪 凭据

默认情况下,Cookie、授权标头和 TLS 证书只能在同源请求上设置!但是,我们可能希望在跨源请求中使用这些凭据。也许我们想在请求中包含 cookies,这样服务器就可以用它来识别用户!

尽管 CORS 在默认情况下不包括凭据,但是我们可以通过添加 Access-Control-Allow-Credentials CORS 标头来更改它!🎉

如果想在跨源请求中包括 cookies 和其他授权标头,我们需要在请求上将 withCredentials 字段设置为 true,并将 Access-Control-Allow-Credentials 标头添加到响应中。

准备就绪!我们现在可以在跨源请求中包含凭据了🥳。


虽然我认为我们都同意 CORS 错误有时会让人沮丧,但令人惊讶的是,它让我们能在浏览器中安全地发出跨源请求(它应该收到更多的爱心,哈哈)✨。

很明显,同源策略和 CORS 的知识比我在这篇博文中所能涵盖的要多得多!如果想多读一些的话,幸好有很多不错的资源,比如这个,或者 W3 规范💪🏼。

和往常一样,请随时联系我!😊

原文:https://dev.to/lydiahallie/cs-visualized-cors-5b8h

欢迎关注我的其它发布渠道