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


111

tl; dr; 关于同一原产地政策

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

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

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


我正在网站上,五分钟前还可以。
彼得·大卫·卡特

1
它会发出CORS标头吗?也许如果您共享一些代码,将更容易看到
Jaromanda X 2016年

可能是。我应该问哪个部门找出来?我只是做ribary.marionette的东西而已...
彼得·大卫·卡特

是的 我想部门部门的组织并不总是统一的,所以这可能是一个模糊的问题,但是我想知道我公司的一些后端/路由/系统管理人员,这似乎是一个很好的借口我自己,所以如果将来有问题我可以帮忙。
彼得·大卫·卡特

我想问一下您操作中服务器端的人。如果您之前能够访问它,那么他们一定已经对您进行了更改。
拉里·莱恩

Answers:


205

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

关于同一原产地政策

这是“ 同源政策”。它是浏览器实现的安全功能。

您的特殊情况显示了如何为XMLHttpRequest实现它(如果使用fetch,您将获得相同的结果),但它也适用于其他内容(例如,将图片加载到<canvas>或将文档加载到<iframe>),仅需略有不同的实现。

(很奇怪,它也适用于CSS字体,但这是因为铸造厂坚持使用DRM,而不是出于Same Origin Policy通常涵盖的安全性问题)。

证明需要SOP的标准方案可以用三个字符来说明:

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

爱丽丝已登录到鲍勃的网站,并且那里有一些机密数据。可能是公司的Intranet(仅可通过LAN上的浏览器访问)或她的在线银行业务(仅可使用输入用户名和密码后获得的cookie进行访问)。

爱丽丝访问了Mallory的网站,该网站具有一些JavaScript,这些JavaScript使爱丽丝的浏览器向鲍勃的网站发出HTTP请求(从她的IP地址,她的cookie等)。这可能与使用XMLHttpRequest和阅读一样简单responseText

浏览器的同源策略禁止JavaScript读取Bob的网站返回的数据(Bob和Alice不想让Mallory访问)。(请注意,例如,您可以使用<img>跨原点的元素来显示图像,因为图像的内容不会暴露给JavaScript(或Mallory)...除非您将画布放入混合中,否则产生相同的来源违反错误)。


为什么在您认为不应使用同一来源政策时

对于任何给定的URL,可能不需要SOP。在这种情况下,有两种常见的情况:

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

…但是浏览器无法知道以上两个条件是否成立,因此信任不是自动的,因此会应用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 /任何请求。仅添加Access-Control-Allow-Origin到特定URL 的CORS实施常常因此而被绊倒。


显然,通过CORS授予许可是Bob只会在以下情况之一时执行的操作:

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

但是我不是鲍勃!

Mallory没有添加此标头的标准机制,因为它必须来自Bob不能控制的网站。

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


错误消息中提到“预检响应”

一些跨源请求已经过时

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

  • 包括凭据(例如Cookie)
  • 无法使用常规HTML表单生成(例如,具有自定义标头或您不能在表单的中使用的Content-Type 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通信时,将解决此问题)。


不透明的回应

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

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

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

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

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

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

从而:

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

替代CORS

JSONP

鲍勃还可以使用JSONP之类的黑客提供数据,这是人们在CORS出现之前进行跨源Ajax的方式。

它通过以JavaScript程序的形式显示数据来工作,该程序将数据注入到Mallory的页面中。

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

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

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

将两个资源移动到一个原始位置

如果运行JS的HTML文档和所请求的URL来自同一来源(共享相同的方案,主机名和端口),则默认情况下,它们的Same Origin Policy会授予权限。不需要CORS。

代理人

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

它将:

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

该服务器端代码可以由第三方(例如CORS Anywhere)编写和托管。请注意以下方面的隐私含义:第三方可以监视谁代理其服务器上的内容。

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

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

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

但是请注意,从他人网站上获取内容并自行显示可能会侵犯版权,这使您可以采取法律行动。

编写除Web应用以外的内容

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

这并不意味着您不能继续使用JavaScript和HTML,而是可以使用其他机制(例如Node-WebKit或PhoneGap)进行分发。

浏览器扩展

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

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

它们也往往只适用于简单的请求(处理预检OPTIONS请求时失败)。

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


其他安全风险

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


摘要

  • 有什么可以做在您的客户端代码,使CORS获得别人别人的服务器。
  • 如果控制服务器,则将请求发送至:向其添加CORS权限。
  • 如果您与控制它的人友好:让他们为它添加CORS权限。
  • 如果是公共服务:
    • 阅读他们的API文档,以了解他们怎么说使用客户端JavaScript进行访问:
      • 他们可能会告诉您使用特定的网址
      • 他们可能支持JSONP
      • 他们可能根本不支持来自客户端代码的跨域访问(这可能是出于安全原因的故意决定,尤其是如果您必须在每个请求中传递个性化的API密钥时)。
    • 确保您不会触发不需要的预检请求。API可能会授予简单请求的权限,但不会授予预检请求的权限。
  • 如果上述方法均不适用:请让浏览器与您的服务器通信,然后让您的服务器从另一台服务器获取数据并继续传递。(还有一些第三方托管服务,它们将CORS标头附加到您可以使用的可公开访问的资源上)。

如果我运行本地LAN的Web服务器,并尝试从IP / URL进行Ajax加载,那可以工作吗?我还没有尝试过。因为我的Web服务器重现json数据将是一个MCU
Ciasto piekarz 16/12/23

@Ciastopiekarz-正常的相同原点/不同原点规则适用。正常的网络路由规则适用。
昆汀

25
我读过的最完整答案,而不仅仅是关于cors的链接。–
pungggi

@Quentin-哇!+1!因此,我要理解的是,如果Alice使用CORS扩展名,服务器认为她的http调用不是来自javascript,而是来自浏览器扩展,并将其视为正常的相同来源请求?
snippetkid

@snippetkid —否。在通常情况下,服务器将在任何响应中发送CORS标头,而不管请求的来源。浏览器负责根据响应上的CORS标头允许或拒绝对JS的数据访问。(当涉及到飞行前请求时,服务器上的情况会变得/ little /更为复杂)
Quentin

3

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

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);
});

3

因为这在公认的答案中没有提到。

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

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

如果您的客户端代码未在请求中使用修复值显式设置受影响的标头(例如“ Accept”),则可能会发生某些客户端确实使用某些“非标准”值自动设置这些标头,从而导致服务器不接受该标头的情况。简单请求-这将为您带来CORS错误。


2

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

在此处了解更多信息:jQuery的CORS错误

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

如何获得跨域资源共享(CORS)的后请求工作

这些链接可能会有所帮助


0

(出于其他原因)对此CORS问题未作进一步阐述。

我目前因其他原因遇到此问题。我的前端也返回了“ Access-Control-Allow-Origin”标头错误。

只是我指出了错误的URL,所以没有正确反映此标头(我一直认为其中确实如此)。本地主机(前端)->调用非安全的http(假定为https),请确保前端的API端点指向正确的协议。


0

我在Chrome控制台中遇到了相同的错误。

我的问题是,我正尝试使用http://而不是前往该网站https://。因此,没有什么可修复的,只需要使用转到相同的站点https


-1

带有标题的“获取”请求转换为“选项”请求。因此发生了Cors政策问题。您必须对服务器实施“选项”请求。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.