Chrome S3 Cloudfront:在初始请求时,没有“Access-Control-Allow-Origin”标头

我有一个网页( https://smartystreets.com/contact ),它使用jQuery从S3通过CloudFront CDN加载一些SVG文件。

在Chrome中,我将打开一个隐身窗口以及控制台。 然后我将加载页面。 在加载页面时,我通常会在控制台中看到6到8个类似于以下内容的消息:

XMLHttpRequest cannot load https://d79i1fxsrar4t.cloudfront.net/assets/img/feature-icons/documentation.08e71af6.svg. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://smartystreets.com' is therefore not allowed access. 

如果我做了一个标准的页面重新载入,甚至多次,我仍然得到相同的错误。 如果我做了Command+Shift+R那么大多数,有时甚至所有的图像都会加载,而不会出现XMLHttpRequest错误。

有时甚至在图像加载之后,我将刷新,并且一个或多个图像将不会加载并再次返回XMLHttpRequest错误。

我已经检查,更改并重新检查了S3和Cloudfront上的设置。 在S3中,我的CORSconfiguration如下所示:

 <?xml version="1.0" encoding="UTF-8"?> <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <CORSRule> <AllowedOrigin>*</AllowedOrigin> <AllowedOrigin>http://*</AllowedOrigin> <AllowedOrigin>https://*</AllowedOrigin> <AllowedMethod>GET</AllowedMethod> <MaxAgeSeconds>3000</MaxAgeSeconds> <AllowedHeader>Authorization</AllowedHeader> </CORSRule> </CORSConfiguration> 

(注意:最初只有<AllowedOrigin>*</AllowedOrigin> ,同样的问题。)

在CloudFront中,分配行为设置为允许HTTP方法: GET, HEAD, OPTIONS 。 caching的方法是一样的。 转发头设置为“白名单”,白名单包括“访问控制请求头,访问控制请求方法,来源”。

事实上,无caching的浏览器重新加载后,它似乎表明,一切都在S3 / CloudFront方面,否则为什么会传递内容。 但是,为什么内容不能在初始页面视图中传递呢?

我在macOS上的Google Chrome上工作。 每次获取文件,Firefox都没有问题。 歌剧从不获取文件。 Safari会在几次刷新后提取图片。

使用curl我不会有任何问题:

 curl -I -H 'Origin: smartystreets.com' https://d79i1fxsrar4t.cloudfront.net/assets/img/phone-icon-outline.dc7e4079.svg HTTP/1.1 200 OK Content-Type: image/svg+xml Content-Length: 508 Connection: keep-alive Date: Tue, 20 Jun 2017 17:35:57 GMT Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET Access-Control-Max-Age: 3000 Last-Modified: Thu, 15 Jun 2017 16:02:19 GMT ETag: "dc7e4079f937e83291f2174853adb564" Cache-Control: max-age=31536000 Expires: Wed, 01 Jan 2020 23:59:59 GMT Accept-Ranges: bytes Server: AmazonS3 Vary: Origin,Access-Control-Request-Headers,Access-Control-Request-Method Age: 4373 X-Cache: Hit from cloudfront Via: 1.1 09fc52f58485a5da8e63d1ea27596895.cloudfront.net (CloudFront) X-Amz-Cf-Id: wxn_m9meR6yPoyyvj1R7x83pBDPJy1nT7kdMv1aMwXVtHCunT9OC9g== 

有人build议我删除CloudFront分配并重新创build它。 看起来像一个相当苛刻和不便的解决办法。

什么是造成这个问题?

更新:

从未能加载的图像添加响应头。

 age:1709 cache-control:max-age=31536000 content-encoding:gzip content-type:image/svg+xml date:Tue, 20 Jun 2017 17:27:17 GMT expires:2020-01-01T23:59:59.999Z last-modified:Tue, 11 Apr 2017 18:17:41 GMT server:AmazonS3 status:200 vary:Accept-Encoding via:1.1 022c901b294fedd7074704d46fce9819.cloudfront.net (CloudFront) x-amz-cf-id:i0PfeopzJdwhPAKoHpbCTUj1JOMXv4TaBgo7wrQ3TW9Kq_4Bx0k_pQ== x-cache:Hit from cloudfront 

你正在为同一个对象提出两个请求,一个来自HTML,一个来自XHR。 第二个失败,因为Chrome使用来自第一个请求的caching响应,该请求没有Access-Control-Allow-Origin响应头。

为什么?

Chromium bug 409090在caching正常的请求之后caching失败的跨源请求描述了这个问题,并且这是一个“无法修复” – 他们认为它们的行为是正确的。 Chrome认为caching的响应是可用的, 显然是因为响应没有包含Vary: Origin头。

但是,即使在存储桶上configuration了CORS,但在没有Origin:请求标头的情况下请求对象时,S3不会返回Vary: OriginVary: Origin只有在请求中存在一个Origin头域时才会发送Origin

而CloudFront即使在Origin被列入转发白名单时也不会添加Vary: Origin ,这应该意味着改变头部可能会修改响应 – 这就是为什么您需要根据请求头进行转发和caching的原因。

CloudFront获得通过,因为如果S3更加正确,则它的响应将是正确的,因为CloudFront在由S3提供时会返回此信息。

S3,有点模糊。 当请求中没有Some-Header时返回Vary: Some-Header 并不是错误的。

例如,包含的响应

Vary: accept-encoding, accept-language

指示原始服务器可能已经使用请求的Accept-EncodingAccept-Language字段(或缺less)作为确定因素,同时select该响应的内容。 (强调加)

https://tools.ietf.org/html/rfc7231#section-7.1.4

很显然, Vary: Some-Absent-Header是有效的,所以如果在configuration了CORS的情况下,将Vary: Origin添加到响应中,那么S3是正确的,因为这确实会改变响应。

而且,显然,这将使Chrome做正确的事情。 或者,如果在这种情况下没有做正确的事情,那就违反了“ MUST NOT 。 从同一部分:

源服务器可能会发送带有两个字段列表的Vary

  1. 要通知caching接收者他们MUST NOT使用这个响应来满足以后的请求,除非后面的请求与原始请求具有相同的值(RFC7234的第4.1节)。 换句话说,Vary扩展了将新请求匹配到所存储的高速caching条目所需的高速caching密钥。

所以,S3真的SHOULD返回Vary: Origin在桶上configurationCORS时的Origin ,如果Origin不在请求中,但不是。

尽pipe如此,S3并不是没有返回头的错误,因为它只是一个SHOULD ,而不是一个MUST 。 同样,从RFC-7231的同一部分:

原始服务器SHOULD发送一个Vary头域,当它的select一个表示的algorithm根据请求消息的方面而不是方法和请求目标,…

另一方面,可以这样说,Chrome应该隐式知道改变Origin头应该是一个caching键,因为它可以改变响应,就像Authorization可以改变响应一样。

…除非不能改变方差,或者故意将原始服务器configuration为防止caching透明。 例如,不需要在Vary发送Authorization字段名称,因为跨用户重用受到字段定义[…]

同样,跨越原点的再利用可以被Origin的本质所限制,但是这个论点并不强。


tl; dr:由于实现的特殊性,您显然无法成功从HTML中获取对象,然后再次成功获取对象,并将其作为CORS请求(使用Chrome和S3(带或不带CloudFront))。


解决方法:

此行为可以通过CloudFront和Lambda @ Edge使用以下代码作为Origin响应触发器来解决。

这增加了Vary: Access-Control-Request-Headers, Access-Control-Request-Method, Origin到S3没有Vary头部的任何响应。 否则,响应中的Vary标题不会被修改。

 'use strict'; // If the response lacks a Vary: header, fix it in a CloudFront Origin Response trigger. exports.handler = (event, context, callback) => { const response = event.Records[0].cf.response; const headers = response.headers; if (!headers['vary']) { headers['vary'] = [ { key: 'Vary', value: 'Access-Control-Request-Headers' }, { key: 'Vary', value: 'Access-Control-Request-Method' }, { key: 'Vary', value: 'Origin' }, ]; } callback(null, response); }; 

归属地:我也是最初共享此代码的AWS支持论坛上原始post的作者。

我不知道为什么你会从各种浏览器得到不同的结果,但是:

X-Amz-Cf-Id:wxn_m9meR6yPoyyvj1R7x83pBDPJy1nT7kdMv1aMwXVtHCunT9OC9g ==

这条线就是CloudFront或Support工程师将用来跟踪其中一个失败的请求(如果可以引起他们注意的话)。 如果请求正在到达CloudFront服务器,则应在响应中包含此标头。 如果该标题不存在,那么请求可能会在到达CloudFront之前失败。