XMLHttpRequest 无法加载 XXX No 'Access-Control-Allow-Origin' 标头

IT技术 javascript cors same-origin-policy
2020-12-12 22:08:54

tl;博士; 关于同源策略

我有一个启动 express.js 服务器实例的 Grunt 进程。直到现在它开始提供一个空白页面时,它一直工作得非常好,Chrome(最新版本)的开发人员控制台的错误日志中出现以下内容:

XMLHttpRequest 无法加载https://www.example.com/ 请求的资源上不存在“Access-Control-Allow-Origin”标头。因此,不允许访问Origin ' http://localhost:4300 '。

是什么阻止我访问该页面?

6个回答

tl;dr — 结尾有摘要,答案中有标题,可以更轻松地找到相关部分。建议阅读所有内容,因为它为理解原因提供了有用的背景,从而更容易了解如何在不同情况下应用。

关于同源策略

这是同源策略它是由浏览器实现的安全功能。

您的特定案例展示了它是如何为 XMLHttpRequest 实现的(如果您使用 fetch,您将获得相同的结果),但它也适用于其他事物(例如加载到 a 上的图像<canvas>加载到 a 中的文档<iframe>),只需使用略有不同的实现。

(奇怪的是,它也适用于 CSS 字体,但那是因为代工厂坚持 DRM 而不是同源策略通常涵盖的安全问题)。

证明需要 SOP 的标准场景可以用三个字符来证明

  • 爱丽丝是一个拥有网络浏览器的人
  • 鲍勃经营一个网站(https://www.[website].com/在你的例子中)
  • Mallory 经营一个网站(http://localhost:4300在你的例子中)

Alice 登录到 Bob 的站点并在那里拥有一些机密数据。也许是公司内部网(只能通过 LAN 上的浏览​​器访问),或者她的网上银行(只能通过输入用户名和密码后获得的 cookie 才能访问)。

Alice 访问 Mallory 的网站,该网站具有一些 JavaScript,导致 Alice 的浏览器向 Bob 的网站发出 HTTP 请求(从她的 IP 地址和她的 cookie 等)。这可能就像使用XMLHttpRequest和阅读responseText.

浏览器的同源策略阻止 JavaScript 读取 Bob 网站返回的数据(Bob 和 Alice 不希望 Mallory 访问该网站)。(请注意,例如,您可以使用<img>跨源元素显示图像,因为图像的内容不会暴露给 JavaScript(或 Mallory)……除非您将画布放入混合中,在这种情况下,您生成同源违规错误)。


为什么当您认为不应该适用同源政策时

对于任何给定的 URL,可能不需要 SOP。出现这种情况的几种常见情况是:

  • Alice、Bob 和 Mallory 是同一个人。
  • 鲍勃提供完全公开的信息

……但浏览器无法知道上述任何一项是否属实,因此信任不是自动的,而是应用了 SOP。在浏览器将其提供给不同网站的数据之前,必须明确授予权限。


为什么同源策略仅适用于网页中的 JavaScript

浏览器扩展*、浏览器开发工具中的网络选项卡和 Postman 等应用程序都是安装软件。它们不会仅仅因为您访问了那个不同的网站就将数据从一个网站传递到属于另一个网站的 JavaScript 安装软件通常需要更谨慎的选择。

没有第三方 (Mallory) 被视为风险。

*浏览器扩展确实需要仔细编写以避免跨域问题。例如,请参阅 Chrome 文档


为什么不用JS读取就可以在页面中显示数据

在许多情况下,Mallory 的站点会导致浏览器从第三方获取数据并显示它(例如,通过添加<img>元素来显示图像)。不过,Mallory 的 JavaScript 不可能读取该资源中的数据,只有 Alice 的浏览器和 Bob 的服务器可以这样做,因此它仍然是安全的。


CORS

错误消息中提到Access-Control-Allow-OriginHTTP响应标头是CORS标准的一部分,该标准允许 Bob 明确授予 Mallory 站点通过 Alice 的浏览器访问数据的权限。

一个基本的实现只包括:

Access-Control-Allow-Origin: *

...在响应头中允许任何网站读取数据。

Access-Control-Allow-Origin: http://example.com/

...将只允许特定站点访问它,并且 Bob 可以根据Origin 请求标头动态生成它以允许多个但不是所有站点访问它。

Bob 如何设置响应头的细节取决于 Bob 的 HTTP 服务器和/或服务器端编程语言。一系列可能有帮助的各种常见配置的指南

应用 CORS 规则的模型

注意:有些请求很复杂,会发送一个预检OPTIONS 请求,在浏览器发送 JS 想要发出的 GET/POST/PUT/Whatever 请求之前,服务器必须响应该请求。仅添加Access-Control-Allow-Origin到特定 URL的 CORS 实现通常会被此绊倒。


显然,通过 CORS 授予权限是 Bob 仅在以下情况下才会执行的操作:

  • 数据不是私人的
  • 马洛里被信任

但我不是鲍勃!

Mallory没有标准机制可以添加此标头,因为它必须来自 Bob 的网站,而她无法控制该网站。

如果 Bob 正在运行公共 API,那么可能有一种机制可以打开 CORS(可能通过以某种方式格式化请求,或登录到 Bob 站点的开发人员门户站点后的配置选项)。不过,这必须是 Bob 实施的一种机制。Mallory 可以阅读 Bob 站点上的文档以查看是否有可用的内容,或者她可以与 Bob 交谈并要求他实施 CORS。


提及“预检响应”的错误消息

一些跨源请求是预检的

当(粗略地说)您尝试发出跨域请求时会发生这种情况:

  • 包括 cookie 等凭据
  • 无法使用常规 HTML 表单生成(例如,具有无法在表单的 中使用的自定义标题或内容类型enctype)。

如果你正确地做一些需要预检的事情

在这些情况下,此答案的其余部分仍然适用,但您还需要确保服务器可以侦听预检请求(将是OPTIONS(而不是GETPOST或您尝试发送的任何内容)并以正确的方式响应它Access-Control-Allow-Origin包头,而且Access-Control-Allow-MethodsAccess-Control-Allow-Headers允许特定HTTP方法或标题。

如果您错误地触发了预检

有时人们在尝试构建 Ajax 请求时会出错,有时这些会触发预检的需要。如果 API 旨在允许跨源请求,但不需要任何需要预检的内容,那么这可能会中断访问。

引发此问题的常见错误包括:

  • 尝试Access-Control-Allow-Origin在请求上放置和其他 CORS 响应标头。这些不属于请求,不做任何有用的事情(您可以授予自己权限的权限系统有什么意义?),并且必须只出现在响应中。
  • 试图Content-Type: application/json在没有请求正文来描述内容的 GET 请求上放置一个标头(通常是当作者混淆Content-Type和 时Accept)。

在这两种情况中的任何一种情况下,删除额外的请求标头通常足以避免需要预检(这将解决与支持简单请求但不支持预检请求的 API 通信时的问题)。


不透明的react

有时您需要发出 HTTP 请求,但不需要读取响应。例如,如果您将日志消息发布到服务器进行记录。

如果您使用的fetchAPI(而不是XMLHttpRequest),那么您可以将其配置为不尝试使用 CORS。

请注意,这不会让您执行需要 CORS 执行的任何操作。您将无法阅读回复。您将无法提出需要预检的请求。

它将让您发出一个简单的请求,看不到响应,并且不会用错误消息填充开发人员控制台。

当您使用fetchCORS发出请求但未获得查看使用 CORS 响应的权限时,Chrome 错误消息解释了如何执行此操作

https://example.com/从源“ https://example.net获取“ ”的访问已被 CORS 策略阻止:Access-Control-Allow-Origin请求的资源上不存在“ ”标头。如果不透明响应满足您的需求,请将请求的模式设置为“no-cors”以在禁用 CORS 的情况下获取资源。

因此:

fetch("http://example.com", { mode: "no-cors" });

CORS 的替代品

JSONP

Bob 还可以使用像JSONP这样的 hack 来提供数据,这就是人们在 CORS 出现之前进行跨源 Ajax 的方式。

它的工作原理是以 JavaScript 程序的形式呈现数据,该程序将数据注入 Mallory 的页面。

它要求 Mallory 信任 Bob 不会提供恶意代码。

请注意共同的主题:提供数据的站点必须告诉浏览器第三方站点可以访问它发送到浏览器的数据。

由于 JSONP 的工作原理是通过附加一个<script>元素来以 JavaScript 程序的形式加载数据,该程序调用页面中已有的函数,因此尝试在返回 JSON 的 URL 上使用 JSONP 技术将失败——通常会出现 CORB 错误——因为 JSON不是 JavaScript。

将两个资源移动到一个 Origin

如果运行 JS 的 HTML 文档和被请求的 URL 来自同一个源(共享相同的方案、主机名和端口),那么它们的同源策略默认授予权限。不需要 CORS。

代理

Mallory可以使用服务器端代码来获取数据(然后她可以像往常一样通过 HTTP 从她的服务器传递到 Alice 的浏览器)。

它要么:

  • 添加 CORS 标头
  • 将响应转换为 JSONP
  • 与 HTML 文档同源

该服务器端代码可以由第三方(例如 CORS Anywhere)编写和托管。请注意这对隐私的影响:第三方可以监控谁在他们的服务器上代理什么。

Bob 不需要为此授予任何权限。

这里没有安全隐患,因为那只是在 Mallory 和 Bob 之间。Bob 无法认为 Mallory 就是 Alice,也无法向 Mallory 提供应在 Alice 和 Bob 之间保密的数据。

因此,Mallory 只能使用这种技术来读取公共数据。

但是请注意,从其他人的网站获取内容并自行显示可能会侵犯版权,并使您面临法律诉讼。

编写 Web 应用程序以外的其他内容

如“为什么同源策略仅适用于网页中的 JavaScript”一节所述,您可以通过不在网页中编写 JavaScript 来避免 SOP。

这并不意味着您不能继续使用 JavaScript 和 HTML,但您可以使用其他一些机制来分发它,例如 Node-WebKit 或 PhoneGap。

浏览器扩展

在应用同源策略之前,浏览器扩展可能会在响应中注入 CORS 标头。

这些对开发很有用,但对生产站点并不实用(要求站点的每个用户安装浏览器扩展来禁用其浏览器的安全功能是不合理的)。

它们也倾向于只处理简单的请求(在处理预检 OPTIONS 请求时失败)。

使用本地开发服务器拥有合适的开发环境 通常是更好的方法。


其他安全风险

请注意,SOP / CORS 不会减轻需要独立处理的XSSCSRFSQL 注入攻击。


概括

  • 有什么可以做在您的客户端代码,使CORS获得别人别人的服务器。
  • 如果您控制服务器,则向其发出请求:向其添加 CORS 权限。
  • 如果您与控制它的人友好:让他们为其添加 CORS 权限。
  • 如果是公共服务:
    • 阅读他们的 API 文档,了解他们对使用客户端 JavaScript 访问它的看法:
      • 他们可能会告诉您使用特定的 URL
      • 他们可能支持 JSONP
      • 它们可能根本不支持来自客户端代码的跨域访问(这可能是出于安全考虑的深思熟虑的决定,特别是如果您必须在每个请求中传递个性化的 API 密钥)。
    • 确保您没有触发不需要的预检请求。API 可能会授予简单请求的权限,但不会授予预检请求的权限。
  • 如果以上都不适用:让浏览器改为与您的服务器通信,然后让您的服务器从其他服务器获取数据并将其传递。(还有第三方托管服务将 CORS 标头附加到您可以使用的可公开访问的资源)。
@snippetkid — 不。在通常情况下,服务器会在响应中发送 CORS 标头,而不关心请求来自哪里。浏览器有责任根据响应中的 CORS 标头允许或拒绝 JS 访问数据。(当涉及到预检请求时,服务器上的事情变得/有点/更复杂)
2021-02-08 22:08:54
@Ciastopiekarz — 适用正常的相同来源/不同来源规则。正常的网络路由规则适用。
2021-02-09 22:08:54
我读过的最完整的答案,而不仅仅是关于 cors 的链接。
2021-02-11 22:08:54
@昆汀 - 哇!+1!所以我要理解的是,如果 Alice 使用 CORS 扩展,服务器认为她的 http 调用不是来自 javascript 而是来自浏览器扩展,并将其视为正常的同源请求?
2021-02-12 22:08:54
如果我运行本地 LAN 网络服务器并尝试从 IP/URL 加载 ajax 会起作用吗?我还没有尝试过。因为我的 Web 服务器返回 json 数据将是一个 MCU
2021-02-16 22:08:54

目标服务器必须允许跨域请求。为了允许它通过 express,只需处理 http 选项请求:

app.options('/url...', function(req, res, next){
   res.header('Access-Control-Allow-Origin', "*");
   res.header('Access-Control-Allow-Methods', 'POST');
   res.header("Access-Control-Allow-Headers", "accept, content-type");
   res.header("Access-Control-Max-Age", "1728000");
   return res.sendStatus(200);
});

因为在接受的答案中没有提到这一点。

  • 对于这个确切的问题,情况并非如此,但可能会帮助其他搜索该问题的人
  • 某些情况下,您可以在客户端代码中执行此操作以防止 CORS 错误

您可以使用简单请求
为了执行“简单请求”,请求需要满足几个条件。例如,仅允许POSTGETHEAD方法,以及只允许某个给定的头(你可以找到所有的条件在这里)。

如果您的客户端代码未在请求中使用固定值显式设置受影响的标头(例如“接受”),则可能会发生某些客户端确实使用一些“非标准”值自动设置这些标头,导致服务器不接受它作为简单请求 - 这会给你一个 CORS 错误。

这是由于 CORS 错误而发生的。CORS 代表跨源资源共享。简而言之,当我们尝试从另一个域访问域/资源时会发生此错误。

在此处阅读更多相关信息:jquery 的 CORS 错误

要解决此问题,如果您有权访问其他域,则必须在服务器中允许 Access-Control-Allow-Origin。这可以添加到标题中。您可以为所有请求/域或特定域启用此功能。

如何让跨域资源共享 (CORS) 发布请求工作

这些链接可能会有所帮助

这个 CORS 问题没有进一步阐述(出于其他原因)。

我目前在不同的原因下遇到这个问题。我的前端也返回“Access-Control-Allow-Origin”标头错误。

只是我指向了错误的 URL,所以这个标题没有正确反映(我一直假设它确实如此)。localhost(前端)-> 调用非安全 http(应该是 https),确保前端的 API 端点指向正确的协议。