假如你刚刚使用最新最好的JavaScript框架构建了一个非常酷的博客应用程序。您脚手架使用应用程序Nuxt.js,使用构建组件Vue.js和使用Vuex状态管理。在后端,您使用Node.js,FeathersJS和Express创建API 。您的后端通过API向您的前端提供博客文章数据。
在将代码推送到服务器之前,您决定测试一切是否正常工作。您的Vue应用程序http://localhost:3000
在API服务器运行时运行http://localhost:8000
。您http://localhost:3000
在Web浏览器中访问并发现错误。没有从API加载数据!不好了!
请保持冷静。当您尝试调试情况,像任何优秀的Web开发人员一样,您可以在浏览器的DevTools中查找答案。
您看到的第一件事是控制台中出现以下错误。
臭名昭着的臭名昭着的CORS Policy Access-Control-Allow-Origin错误
“好吧!”,你对自己说,“API中可能存在一个错误。我只会使用Postman来调试API!“ 你打开邮递员并发送GET请求[http://localhost:8000/api](http://localhost:8000/api.?source=post_page---------------------------)
。
这是你得到的:
成功的API响应
“什么?API正在运作?那为什么我的JavaScript代码失败了呢?“
我甚至不记得我发现自己处于相同情况的次数。我总是谷歌错误并得到一些StackOverflow答案告诉我添加CORS支持。而且我会做,通过添加Access-Control-Allow-Header:*
到HTTP响应的后端。这将解决问题。但是我从来没有真正试图理解这个错误背后的实际原因。
最近,我决定一劳永逸地揭开这个神秘的底层。所以我选择了CORS in Action并深入研究了CORS。在这篇文章中,我将向您介绍我所理解的CORS机制的要点。
浏览器和客户端之间的区别
现在,在我们开始讨论之前,我想澄清一些技术术语。
当我们谈论AJAX调用时,浏览器和客户端是不同的东西。
令人震惊吧?我知道。
当我们谈论HTTP请求/响应周期时,我们主要使用术语“客户端”来引用用户的浏览器发起请求。但是,在AJAX调用中,实际客户端是进行调用的JavaScript代码。浏览器只是充当客户端和服务器之间的中间人。
每当我们从JavaScript发起HTTP请求时,无论是通过XMLHttpRequest API还是通过Fetch API,在该请求实际发送到服务器之前,浏览器都会拦截请求并可以修改它或拒绝将其发送到服务器。在某些情况下,拒绝发送请求是因为同源策略。
在我解释同源策略之前,让我们看一下原点是什么以及它是如何工作的。
什么是Origin?
HTTP请求中的来源来自JS脚本所在的网页。它是一个HTTP标头。它的值是URL中的方案,主机和端口的组合。例如,在以下URL中
http://localhost:8000
https://api.alazierplace.com:3000/api/
https://alazierplace.com/2019/05/data-driven-testing-how-we-went-from-150-test-cases-to-1/
URL方案或协议是URL之前的第一部分://
。所以在我们的案例中,这些计划将是http
和https
。主机被定义为IP地址或域,因此在我们的情况下将是localhost
,api.alazierplace.com
或alazierplace.com
。最后,端口是冒号后面的数字,即8000
或3000
。如果未指定端口,则使用默认HTTP端口,即80
HTTP URL和443
HTTPS URL。所以上面的URL的来源分别如下:
- http://localhost:8000
- https://api.alazierplace.com:3000
- https://alazierplace.com
每当页面中的JavaScript脚本向某个API发出HTTP请求时,浏览器会在将请求发送到服务器之前添加Origin标头(包括其他标头)。
Same-Origin Policy
我们在上述场景中遇到的问题是由于我们今天使用的大多数浏览器实现的Same-Origin Policy。此策略规定从一个源加载的脚本或文档无法自由交互或请求来自其他来源的数据。默认情况下,从源加载的网页https://alazierplace.com
只能从同一个源或服务器请求资源(css,jpeg或png文件等),即[https://alazierplace.com](https://alazierplace.com./?source=post_page---------------------------)
。
因此,当您打开我的网站时,您的浏览器首先要求的是HTML。HTML包含指向具有以下URL的CSS样式表的链接
https://alazierplace.com/wp-content/cache/autoptimize/css/autoptimize_4fe4785dcf517d3a732df2418e95c09e.css
对CSS文件的请求成功,因为CSS文件的来源与请求文件的网页相同。
如果CSS文件托管在另一个来源,例如
[https://someotherwebsite.com/css/some-cool-stylesheet.css](https://someotherwebsite.com/css/some-cool-stylesheet.css?source=post_page---------------------------)
然后浏览器不会让该请求成功,因为它正在请求来自不同来源的资源。这是一项关键的安全措施,可以降低恶意脚本和文档损害用户和其他网站的可能性。需要注意的重要一点是,相同的源策略是在Web浏览器中实现的纯客户端度量。
现在您可能会问,API通常托管的主要原因不同于主Web应用程序,API调用和请求成功,那么它是如何工作的?
好问题。简而言之就是CORS机制,它允许服务器控制哪些来源可以从中请求资源,哪些不能。
CORS - 跨源资源共享
CORS是跨源资源共享的缩写。它是W3C推荐的Web标准,它使Web客户端(即在浏览器中运行的脚本)能够访问来自其他来源的内容。
Web服务器通过使用HTTP响应头,可以决定要提供或拒绝的源,要允许的HTTP方法以及其他选项。然后,实现CORS策略的Web浏览器将检查CORS相关的响应头并相应地执行操作。CORS将内容的控制权交给服务器。
跨源请求周期
因此,制作跨源请求时的基本流程如下所示:
CORS请求的请求/响应周期
好吧,让我们打破一下吧?
- JS客户端启动AJAX调用。
- 浏览器拦截请求,在将此请求发送到服务器之前,它会发送一个飞行前请求。飞行前请求是HTTP选项请求。
- 飞行前请求的响应包含包含服务器的CORS配置的HTTP头。
- 一旦通过飞行前请求验证了CORS策略,则将实际请求发送到服务器。
- 服务器返回响应,然后由浏览器传递给客户端。
Pre-Flight 请求
这里有趣的是Pre-Flight请求。它只是一个HTTP OPTIONS请求,其响应包含指定CORS配置的HTTP头。飞行前请求仅发送一次,然后由浏览器缓存。
不会在每个CORS请求上发送Pre-Flight请求。仅在满足以下条件时才会发送:
- 请求使用比其它的HTTP方法
HEAD
,GET
或POST
。 - 的
Content-Type
请求报头具有比以外的值application/x-www-form-urlencoded
,multipart/form-data
和text/plain
。 - 该请求将高于其他头
Accept
,Accept-Language
和Content-Language
。
这些统称为简单方法和简单标题。
CORS标题
以下是CORS中使用的HTTP响应头:
- Access-Control-Allow-Origin - 最流行的CORS标头,因为它是第一个出现在大多数错误中的标头。它的值可以是单个原点或通配符
*
值。如果列出了单个源,则只有来自该源的脚本可以向服务器请求资源。如果提供了通配符*
值,则服务器接受来自任何源的请求。这对于创建公共API很有用。 - Access-Control-Expose-Headers - 此标头列出了列入白名单且可由客户端访问的标头。服务器在响应中发送这些标头。
- Access-Control-Allow-Headers - 此标头列出了实际请求中允许的HTTP标头。
- Access-Control-Allow-Methods - 此标头列出了访问资源时服务器允许的HTTP方法。
- Access-Control-Allow-Credentials - 此标头指示实际请求是否可以包含凭据,即cookie和可用于标识用户的其他信息。
- Access-Control-Max-Age - 此标头包含Pre-Flight请求在浏览器中缓存的秒数。如果时间已到期,则在下一次AJAX呼叫时发出另一个飞行前请求。
把它们放在一起
好的,我们已经看到了CORS请求是什么以及它是如何工作的。但问题可能出现,为什么需要这样一种复杂的机制来在JavaScript中发出HTTP请求?我们可以轻松地从任何其他语言发出HTTP请求,而不是为什么JavaScript包含这些限制?
答案就在于语言的执行环境之间的差异。传统的编程语言,如Python,Ruby和Java等,大多数都运行在服务器或桌面应用程序上。这些环境相对更能控制程序员。甚至运行JavaScript的Node.js也没有CORS限制。
但是大多数JavaScript程序都是通过网络传递给用户浏览器的。它们必须在用户设备的用户浏览器中执行。该浏览器可能包含一些敏感的登录信息,或者可能被恶意页面加载的JavaScript利用的身份验证cookie或会话数据。
在浏览器中实现同源策略以防止任何脚本不受限制地访问其他来源。CORS策略也在浏览器中实现,它为JavaScript代码提供了一种受控且安全的机制,可以在保持服务器控制服务对象和服务对象的同时控制某些资源。
注意事项
与所有内容一样,在处理CORS配置时应注意一些注意事项和注意事项。
- 当站点在负载均衡器或代理服务器后面提供时,Pre-Flight请求可能会引入一些缓存问题。在这种情况下,请求的来源可能会有所不同,因此未正确缓存Pre-Flight请求。这可以通过向
Origin
标头添加标头来减轻Vary
。 - Web浏览器实现了同源和CORS策略。如果用户使用的Web浏览器不支持这些机制,则CORS请求将作为普通的HTTP请求。
- CORS不是进行跨源通信的唯一方式。JSONP,postMessage和服务器端请求都可以绕过浏览器的CORS策略。
说明
为了准备这篇博文,我读了一本完整的书。那本书是Monsur Hossain的CORS in Action。这是一本特殊的书,其中的代码示例以优秀的方式解释了CORS。
对于希望深入了解跨域通信的所有Web开发人员来说,绝对可以阅读Monsur Hossains的书。这非常值得投资。
转:https://medium.com/swlh/cors-cross-origin-communication-in-the-modern-web-1a8f6ec3b3a6