XMLHttpRequest 无法加载 XXX 没有“访问控制-允许-源”标头关于同源策略为什么同源策略在您认为不应该适用时适用为什么同源策略仅适用于网页中的 JavaScript为什么您可以在页面中显示数据而无需使用JS读取它CORS提及“印前检查的响应”的错误消息不透明响应(模式)no-corsCORS 的替代方案浏览器扩展其他安全风险总结

2022-08-30 02:27:10

tl;博士;关于同源策略

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

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

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


答案 1

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

关于同源策略

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

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

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

演示 SOP 需求的标准方案可以使用三个字符进行演示:

  • 爱丽丝是一个拥有网络浏览器的人
  • Bob 运行一个网站(在您的示例中)https://www.[website].com/
  • 马洛里经营一个网站(在你的例子中)http://localhost:4300

爱丽丝登录了鲍勃的网站,并在那里有一些机密数据。也许是公司内部网(只能通过LAN上的浏览器访问)或她的网上银行(只能通过输入用户名和密码后获得的cookie访问)。

爱丽丝访问了Mallory的网站,该网站有一些JavaScript,导致Alice的浏览器向Bob的网站发出HTTP请求(从她的IP地址和她的cookie等)。这可以像使用和读取 .XMLHttpRequestresponseText

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


为什么同源策略在您认为不应该适用时适用

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

  • 爱丽丝,鲍勃和马洛里是同一个人。
  • 鲍勃提供完全公开的信息

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


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

浏览器扩展,浏览器开发人员工具中的“网络”选项卡和Postman等应用程序都已安装软件。他们不会仅仅因为您访问了另一个网站而将数据从一个网站传递到属于另一个网站的JavaScript。安装软件通常需要更明智的选择。*

没有第三方(马洛里)被认为是风险。

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


为什么您可以在页面中显示数据而无需使用JS读取它

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


CORS

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

基本实现仅包括:

Access-Control-Allow-Origin: *

...以允许任何网站读取数据。

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

...将只允许特定站点访问它,Bob 可以根据请求标头动态生成该站点,以允许多个(但不是全部)站点访问它。Origin

Bob 如何设置该响应标头的细节取决于 Bob 的 HTTP 服务器和/或服务器端编程语言。Node.js/Express.js的用户应使用记录良好的 CORS 中间件。其他平台的用户应该查看此指南集,了解可能有帮助的各种常见配置

Model of where CORS rules are applied

注意:有些请求很复杂,会发送一个预检选项请求,服务器必须响应该请求,然后浏览器才会发送GET/POST/PUT/JS想要发出的任何请求。仅添加到特定 URL 的 CORS 实现经常会因此而绊倒。Access-Control-Allow-Origin


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

  • 数据不是私有的或
  • 马洛里值得信赖

如何添加这些标头?

这取决于您的服务器端环境。

如果可以,请使用旨在处理 CORS 的库,因为它们将为您提供简单的选项,而不必手动处理所有内容。

Enable-Cors.org 提供了一个针对特定平台和框架的文档列表,您可能会发现这些文档和框架很有用。

但我不是鲍勃!

马洛里没有标准的机制来添加这个标题,因为它必须来自鲍勃的网站,她无法控制。

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


提及“印前检查的响应”的错误消息

某些跨域请求是预检的

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

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

如果您正确地执行了需要预检的操作

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

如果您错误地触发了印前检查

有时人们在尝试构造 Ajax 请求时会犯错误,有时这些错误会触发对预检的需求。如果 API 设计为允许跨域请求,但不需要任何需要预检的内容,则可能会中断访问。

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

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

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


不透明响应(模式)no-cors

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

如果您使用的是提取 API(而不是 ),则可以将其配置为不尝试使用 CORS。XMLHttpRequest

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

它允许您发出简单的请求,看不到响应,并且不会在开发人员控制台中填充错误消息。

当您使用CORS发出请求并且未获得查看使用CORS的响应的权限时,Chrome错误消息会说明如何执行此操作:fetch

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

因此:

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

CORS 的替代方案

JSONP

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

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

它要求马洛里信任鲍勃不提供恶意代码。

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

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

将两个资源移动到单个源

如果运行 JS 的 HTML 文档,并且所请求的 URL 位于同一源(共享相同的方案、主机名和端口),则默认情况下,它们的同源策略授予权限。不需要 CORS。

代理

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

它将:

  • 添加 CORS 标头
  • 将响应转换为 JSONP
  • 存在于与 HTML 文档相同的源上

该服务器端代码可以由第三方(例如CORS Anywhere)编写和托管。请注意这样做的隐私含义:第三方可以监视谁在其服务器上代理了什么。

Bob 不需要授予任何权限即可实现此目的。

这里没有安全隐患,因为这只是在马洛里和鲍勃之间。鲍勃没有办法认为马洛里就是爱丽丝,也无法向马洛里提供应该在爱丽丝和鲍勃之间保密的数据。

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

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

编写 Web 应用以外的内容

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

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

浏览器扩展

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

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

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

使用本地开发服务器设置适当的开发环境通常是更好的方法。


其他安全风险

请注意,SOP / CORS不能缓解需要独立处理的XSSCSRFSQL注入攻击。


总结

  • 客户端代码中,您无法执行任何操作来启用对其他人的服务器的 CORS 访问。
  • 如果控制要向其发出请求的服务器:向其添加 CORS 权限。
  • 如果你与控制它的人友好:让他们向其添加 CORS 权限。
  • 如果是公共服务:
    • 阅读他们的API文档,看看他们对使用客户端JavaScript访问它的看法:
      • 他们可能会告诉您使用特定的网址
      • 他们可能支持 JSONP
      • 他们可能根本不支持从客户端代码进行跨域访问(这可能是出于安全原因的深思熟虑的决定,特别是如果您必须在每个请求中传递个性化的API密钥)。
    • 确保没有触发不需要的预检请求。API 可能会为简单请求授予权限,但不会授予预检请求的权限。
  • 如果上述情况均不适用:请让浏览器改为与您的服务器通信,然后让您的服务器从另一台服务器获取数据并将其传递。(还有一些第三方托管服务将 CORS 标头附加到您可以使用的可公开访问的资源)。

答案 2

目标服务器必须允许跨源请求。为了允许它通过快递,只需处理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);
});