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

Pet*_*ter 92 javascript same-origin-policy cors

TL;博士; 关于同源政策

我有一个Grunt进程,它启动了express.js服务器的实例.刚刚开始提供空白页面,并且Chrome中的开发人员控制台的错误日志中出现以下内容(最新版本),这一点工作非常精细:

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

什么阻止我访问该页面?

Que*_*tin 155

关于同源政策

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

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

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

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

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

Alice登录Bob的网站,并在那里有一些机密数据.也许它是一个公司内部网(只能由局域网上的浏览器访问),或者她的网上银行(只能通过输入用户名和密码后获得的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)被视为风险.


为什么你可以在不用JS阅读的情况下在页面中显示数据

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


CORS

Access-Control-Allow-Origin错误消息中引用的标头是CORS标准的一部分,它允许Bob明确授予Mallory网站的权限,以通过Alice的浏览器访问数据.

基本实现只包括:

Access-Control-Allow-Origin: *
Run Code Online (Sandbox Code Playgroud)

......允许任何网站阅读数据.

Access-Control-Allow-Origin: http://example.com/
Run Code Online (Sandbox Code Playgroud)

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

设置响应头的具体细节取决于Bob的HTTP服务器和/或服务器端编程语言.有一系列指南可供各种常见配置使用.

应用CORS规则的模型

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


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

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

如果您在此方案中也是Bob,那么您添加CORS权限标头的具体细节将取决于您选择的HTTP服务器软件的某些组合以及您用于服务器端编程的语言(如果有).

Mallory无法添加此标题,因为她必须获得Bob的网站的许可,并且为了能够授予自己许可,这将是愚蠢的(使SOP变得毫无用处).


提示"预检响应"的错误消息

一些交叉原始请求是预检的.

这种情况发生在(大致说来)您尝试制作跨源请求时:

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

请注意,"自定义标头"包括OPTIONS和其他CORS响应标头.这些不属于请求,不做任何有用的事情(你可以授予自己权限的权限系统的重点是什么?),并且必须只出现在响应上.

在这些情况下,这个答案的其余部分仍然适用,但您还需要确保服务器可以监听预检请求(这将是GET(而不是POST,Access-Control-Allow-Origin或者您尝试发送的任何内容)并以正确的方式响应它Access-Control-Allow-Methods头,但也Access-Control-Allow-HeadersAccess-Control-Allow-Origin允许特定HTTP方法或标题.


CORS的替代品

JSONP

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

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

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

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

由于JSONP通过附加一个Content-Type: application/json元素以JavaScript程序的形式加载数据,该程序调用页面中已有的函数,因此尝试在返回JSON的URL上使用JSONP技术将失败 - 通常会出现CORB错误 - 因为JSON不是JavaScript.

将两个资源移动到单个Origin

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

代理人

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

它将:

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

服务器端代码可以由第三方(例如YQL)托管.

Bob不需要为此发布任何权限.

这很好,因为那只是马洛里和鲍勃之间.Bob没有办法认为Mallory是Alice并且为Mallory提供了应该在Alice和Bob之间保密的数据.

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

写一些Web应用程序以外的东西

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

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

浏览器扩展

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

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

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

拥有适当的开发环境和本地开发服务器 通常是一种更好的方法.


其他安全风险

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


摘要

  • 有什么可以做在您的客户端代码,使CORS获得别人别人的服务器.
  • 如果您控制服务器,则请求:向其添加CORS权限.
  • 如果您对控制它的人很友好:让他们为其添加CORS权限.
  • 如果是公共服务:请阅读他们的API文档,了解他们对使用客户端JavaScript访问它的看法.他们可能会告诉您使用特定的URL或使用JSONP(或者他们可能根本不支持它).
  • 如果以上都不适用:让浏览器改为与您的服务器通信,然后让您的服务器从其他服务器获取数据并将其传递.(还有第三方托管服务将CORS标头附加到您可以使用的公共可访问资源).

  • 我读过的最完整的答案,而不只是关于cors的链接.. (18认同)

Dap*_*que 5

目标服务器必须允许跨域请求。为了允许它通过 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);
});
Run Code Online (Sandbox Code Playgroud)