Chrome S3 Cloudfront:初始 XHR 請求中沒有“Access-Control-Allow-Origin”標頭
我有一個網頁(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.
如果我對頁面進行標準重新載入,即使是多次,我也會繼續收到相同的錯誤。如果我這樣做
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>
(注意:最初只有
<AllowedOrigin>*</AllowedOrigin>
,同樣的問題。)在 CloudFront 中,分配行為設置為允許 HTTP 方法:
GET, HEAD, OPTIONS
. 記憶體方法是一樣的。Forward Headers 設置為“Whitelist”,該白名單包括“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==
有些人建議我刪除 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
您對同一個對象發出兩個請求,一個來自 HTML,一個來自 XHR。第二個失敗,因為 Chrome 使用了第一個請求的記憶體響應,它沒有
Access-Control-Allow-Origin
響應頭。為什麼?
Chromium 錯誤 409090 記憶體正常請求後來自記憶體的跨域請求失敗描述了此問題,這是“無法修復”——他們認為他們的行為是正確的。Chrome 認為記憶體的響應是可用的,顯然是因為響應不包含
Vary: Origin
標頭。
Vary: Origin
但是,當在沒有請求標頭的情況下請求對象時,S3 不會返回Origin:
,即使在儲存桶上配置了 CORS。 僅當請求中存在標頭Vary: Origin
時才發送。Origin
Vary: Origin
CloudFront即使被列入轉發白名單也不會添加Origin
,根據定義,這應該意味著改變標頭可能會修改響應——這就是您轉發和記憶體請求標頭的原因。CloudFront 獲得通過,因為如果 S3 更正確,它的響應將是正確的,因為 CloudFront 在 S3 提供它時會返回它。
S3,有點模糊。當請求中沒有時返回並沒有錯。
Vary: Some-Header``Some-Header
例如,一個響應包含
Vary: accept-encoding, accept-language
表示源伺服器 在選擇此響應的內容時可能已使用請求
Accept-Encoding
和Accept-Language
欄位**(或缺少)作為決定因素。**(重點補充)顯然,
Vary: Some-Absent-Header
是有效的,因此如果配置了 CORS,則如果將 S3 添加到其響應中,S3 將是正確Vary: Origin
的,因為這確實可能會改變響應。而且,顯然,這將使 Chrome 做正確的事。或者,如果在這種情況下它沒有做正確的事情,它將違反
MUST NOT
. 從同一部分:源伺服器可能
Vary
出於兩個目的發送欄位列表:
- 通知記憶體接收者他們
MUST NOT
使用此響應來滿足後面的請求,除非後面的請求具有與原始請求相同的所列欄位值(第 4.1 節)$$ RFC7234 $$)。換句話說,Vary 擴展了將新請求與儲存的記憶體條目匹配所需的記憶體鍵。…
因此,當在儲存桶上配置 CORS 時,S3 確實
SHOULD
會返回Vary: Origin
,如果Origin
請求中不存在,但它沒有。儘管如此,S3 不返回標頭並不是完全錯誤的,因為它只是 a
SHOULD
,而不是 aMUST
。同樣,來自 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); };
歸因:我也是最初共享此程式碼的 AWS Support 論壇上原始文章的作者。
上面的 Lambda@Edge 解決方案會產生完全正確的行為,但根據您的具體需求,這裡有兩個您可能會發現有用的替代方案:
替代方案/Hackaround #1:在 CloudFront 中偽造 CORS 標頭。
CloudFront 支持添加到每個請求的自定義標頭。如果您設置
Origin:
每個請求,即使是那些不是跨域的,這將在 S3 中啟用正確的行為。配置選項稱為自定義源頭,“源”一詞的含義與 CORS 中的含義完全不同。在 CloudFront 中配置這樣的自定義標頭會使用指定的值覆蓋在請求中發送的內容,或者如果不存在則添加它。如果您只有一個來源通過 XHR 訪問您的內容,例如https://example.com
,您可以添加它。使用*
是可疑的,但可能適用於其他場景。仔細考慮其影響。替代方案/Hackaround #2:使用一個“虛擬”查詢字元串參數,該參數對於 HTML 和 XHR 不同,或者其中一個不存在。這些參數通常被命名
x-*
但不應該被命名x-amz-*
。假設你組成了這個名字
x-request
。所以<img src="https://dzczcexample.cloudfront.net/image.png?x-request=html">
。從 JS 訪問對象時,不要添加查詢參數。CloudFront 已經在做正確的事情,通過使用Origin
標頭或不作為記憶體鍵的一部分記憶體對象的不同版本,因為您在記憶體行為中轉發了該標頭。問題是,您的瀏覽器不知道這一點。這讓瀏覽器相信這實際上是一個單獨的對象,需要在 CORS 上下文中再次請求。如果您使用這些替代建議,請使用其中一個 - 而不是同時使用兩者。