Chrome S3 Cloudfront:初始 XHR 请求中没有“Access-Control-Allow-Origin”标头

Sun*_*arc 45 google-chrome amazon-s3 amazon-cloudfront jquery cors

我有一个网页 ( https://smartystreets.com/contact ),它使用 jQuery 通过 CloudFront CDN 从 S3 加载一些 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.
Run Code Online (Sandbox Code Playgroud)

如果我对页面进行标准重新加载,甚至多次,我仍然会遇到相同的错误。如果我这样做,Command+Shift+R那么大多数,有时是全部图像将在没有XMLHttpRequest错误。

有时即使在图像加载后,我也会刷新并且一个或多个图像不会加载并XMLHttpRequest再次返回该错误。

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

<?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>
Run Code Online (Sandbox Code Playgroud)

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

在 CloudFront 中,分配行为设置为允许 HTTP Methods: GET, HEAD, OPTIONS。缓存方法是相同的。Forward Headers 设置为“白名单”,该白名单包括“Access-Control-Request-Headers、Access-Control-Request-Method、Origin”。

它在无缓存浏览器重新加载后工作的事实似乎表明 S3/CloudFront 方面一切正常,否则为什么要交付内容。但是,为什么内容不会在初始页面视图中传递?

我在 macOS 上的 Google Chrome 中工作。Firefox 每次获取文件都没有问题。Opera 从不获取文件。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==
Run Code Online (Sandbox Code Playgroud)

有人建议我删除 CloudFront 分配并重新创建它。似乎是一个相当苛刻和不方便的修复。

是什么导致了这个问题?

更新:

从加载失败的图像中添加响应标头。

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
Run Code Online (Sandbox Code Playgroud)

Mic*_*bot 88

您对同一个对象发出两个请求,一个来自 HTML,一个来自 XHR。第二个失败,因为 Chrome 使用来自第一个请求的缓存响应,它没有Access-Control-Allow-Origin响应头。

为什么?

Chromium bug 409090 Cross-origin request from cache failed after常规请求被缓存描述了这个问题,这是一个“不会修复”——他们认为他们的行为是正确的。Chrome 认为缓存的响应是可用的,显然是因为响应不包含Vary: Origin标头。

但是,Vary: Origin当在没有Origin:请求标头的情况下请求对象时,即使在存储桶上配置了 CORS,S3 也不会返回。 Vary: Origin仅当Origin请求中存在标头时才发送。

并且 CloudFrontVary: Origin即使在Origin被列入转发白名单,根据定义,这应该意味着改变标头可能会修改响应——这就是您针对请求标头转发和缓存的原因。

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

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

例如,一个响应包含

Vary: accept-encoding, accept-language

表示源服务器在选择此响应的内容时可能已使用请求 Accept-EncodingAccept-Language字段(或缺少)作为决定因素。(强调)

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

显然,Vary: Some-Absent-Header是有效的,因此Vary: Origin如果配置了 CORS ,则 S3 如果将其添加到其响应中将是正确的,因为这确实会改变响应。

而且,显然,这将使 Chrome 做正确的事情。或者,如果在这种情况下它没有做正确的事情,它将违反MUST NOT. 来自同一部分:

源服务器可能Vary出于两个目的发送字段列表:

  1. 通知缓存接收者他们MUST NOT使用此响应来满足稍后的请求,除非稍后的请求与原始请求具有相同的列出字段的值([RFC7234] 的第 4.1 节)。换句话说,Vary 扩展了将新请求与存储的缓存条目匹配所需的缓存键。

...

因此,当在存储桶上配置 CORS 时,S3 真的SHOULD会返回Vary: Origin,如果Origin请求中不存在,但事实并非如此。

尽管如此,S3 不返回标头并不是绝对错误的,因为它只是 a SHOULD,而不是 a MUST。同样,来自 RFC-7231 的同一部分:

当源服务器SHOULD选择表示的算法根据请求消息的其他方面而不是方法和请求目标而变化时,它会发送一个 Vary 标头字段,...

另一方面,可以提出这样的论点,即 Chrome 应该隐式地知道改变Origin标头应该是一个缓存键,因为它可以以同样的方式Authorization改变响应来改变响应。

...除非无法跨越差异或故意配置源服务器以防止缓存透明。例如,不需要将Authorization字段名称发送进来,Vary因为跨用户的重用受到字段定义 [...]

类似地,跨源重用可以说受到 的性质的限制,Origin但这种论点并不充分。


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


解决方法:

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

这会添加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);
};
Run Code Online (Sandbox Code Playgroud)

署名:我也是AWS Support 论坛上最初共享此代码的原始帖子的作者。


上面的 Lambda@Edge 解决方案会产生完全正确的行为,但根据您的特定需求,这里有两个您可能会觉得有用的替代方案:

替代方案/解决方法 #1:在 CloudFront 中伪造 CORS 标头。

CloudFront 支持添加到每个请求的自定义标头。如果您Origin:对每个请求进行设置,即使是那些非跨域请求,这将在 S3 中启用正确的行为。配置选项称为自定义源头,“源”一词的含义与 CORS 中的含义完全不同。在 CloudFront 中配置这样的自定义标头会使用指定的值覆盖请求中发送的内容,或者如果不存在则添加它。如果您只有一个来源通过 XHR 访问您的内容,例如https://example.com,您可以添加它。使用*是可疑的,但可能适用于其他场景。仔细考虑其含义。

替代方案/解决方法 #2:使用一个“虚拟”查询字符串参数,该参数对于 HTML 和 XHR 不同,或者在其中一个或另一个中不存在。这些参数通常被命名x-*但不应该被命名 x-amz-*.

假设你编造了名字x-request。所以<img src="https://dzczcexample.cloudfront.net/image.png?x-request=html">。从JS访问对象时,不要添加查询参数。CloudFront 已经在做正确的事情,通过使用Origin标头或不存在标头作为缓存键的一部分来缓存不同版本的对象,因为您在缓存行为中转发了该标头。问题是,您的浏览器不知道这一点。这让浏览器相信这实际上是一个单独的对象,需要在 CORS 上下文中再次请求。

如果您使用这些替代建议,请使用其中之一——而不是两者。

  • 你的回答是救命稻草,很好的答案。你为我节省了一些时间。 (12认同)
  • @Jeffin,上面的替代方案 #2 仅适用于 S3,无需 CloudFront。添加任意的`?x-some-key=some-value` 查询字符串参数将使浏览器相信请求是不同的。 (3认同)

小智 7

截至 2021 年 11 月,CloudFront 直接支持响应标头策略。其中包括 CORS、安全性和自定义标头。不再需要通过 Lambda@Edge 或 CloudFront Functions 注入自定义标头。

也许更令人高兴的是,不再需要添加 Vary 作为自定义标头。Headers Policies 中的新 CORS 实现包括用于根据获取标准设置适当标头(例如 Vary)的附加逻辑。