Chrome S3 Cloudfront: ไม่มีส่วนหัว 'Access-Control-Allow-Origin' ในคำขอ XHR เริ่มต้น


30

ฉันมีหน้าเว็บ ( https://smartystreets.com/contact ) ที่ใช้ jQuery เพื่อโหลดไฟล์ SVG บางไฟล์จาก S3 ผ่านทาง CloudFront CDN

ใน 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 พฤติกรรมการจัดจำหน่ายเป็นชุดที่จะช่วยให้วิธี GET, HEAD, OPTIONShttp: วิธีการแคชเหมือนกัน ไปข้างหน้าส่วนหัวถูกตั้งค่าเป็น "รายการที่อนุญาต" และรายการที่อนุญาตนั้นรวมถึง "การเข้าถึงการควบคุมการร้องขอส่วนหัวการเข้าถึงการควบคุมการร้องขอวิธีการกำเนิด

ความจริงที่ว่ามันทำงานได้หลังจากโหลดเบราว์เซอร์น้อยแคชดูเหมือนว่าบ่งบอกว่าทุกอย่างเป็นอย่างดีในด้าน S3 / CloudFront อื่นเหตุผลที่จะส่งเนื้อหา แต่ทำไมจะไม่ส่งเนื้อหาในมุมมองหน้าเริ่มต้น

ฉันกำลังทำงานใน Google Chrome บน macOS Firefox ไม่มีปัญหาในการรับไฟล์ทุกครั้ง Opera NEVER ไม่ได้รับไฟล์ 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

คุณถูกต้อง - ลบและสร้างใหม่สุดขีดและไม่ควรจำเป็น คุณช่วยแสดงให้เราเห็นคำขอของเบราว์เซอร์และส่วนหัวการตอบสนองสำหรับคำขอที่ล้มเหลวได้หรือไม่? และบางทีสำหรับคำขอที่ประสบความสำเร็จของวัตถุเดียวกันที่แน่นอน?
Michael - sqlbot

@ Michael-sqlbot ฉันหวังว่าคุณจะไปที่ URL ( smartystreets.com/contact ) และดูว่ามีสิ่งเดียวกันเกิดขึ้นในเครื่องของคุณหรือไม่ :) สิ่งที่น่าสนใจเกี่ยวกับข้อผิดพลาดคือนอกเหนือจากข้อผิดพลาดในคอนโซลเบราว์เซอร์รายงานสถานะ 200 โดยอ้างว่าใช้ภาพ "(จากดิสก์แคช)" ซึ่งไม่ควรเป็นไปได้ด้วย Incognito ฉัน ความคิด แม้หลังจากที่ฉันล้างแคชในท้องถิ่น
SunSparc

1
ใช่คนมักจะ "สร้าง" ชื่อโดเมน (กลายเป็นเว็บไซต์จริง แต่ไม่ใช่เว็บไซต์ที่เป็นปัญหา) ซึ่งตอนแรกฉันไม่รู้ว่าคุณได้ให้ลิงค์จริงที่ถูกต้องกับเว็บไซต์ของคุณ ขอบคุณสำหรับสิ่งนั้นคุณสามารถเพิกเฉยต่อคำขอของฉันได้ ฉันสามารถทำซ้ำปัญหา ดูเหมือนว่าปัญหาด้านลูกค้า ฉันกำลังไล่ตามทฤษฎี
Michael - sqlbot

ฉันคิดว่าคุณอาจถูกต้องเกี่ยวกับเรื่องนี้ว่าเป็นปัญหาด้านลูกค้า รูปภาพเชื่อมโยงกับแท็ก A ใน HTML และดูเหมือนว่าจะมีการร้องขออีกครั้งใน jQuery บางทีข้อผิดพลาดมาจากการโทรหนึ่งครั้งและ 200 สายมาจากอีกสาย
SunSparc

1
นั่นคือสิ่งที่ฉันเชื่อว่าเป็นจริง Chrome และ S3 กำลังโต้ตอบกันในลักษณะที่ทำลายคำขอ CORS ที่ตามคำขอที่ไม่ใช่ CORS สำหรับวัตถุเดียวกัน เนื้อหาทั้งคู่นั้นผิด ... แต่เนื้อหาทั้งคู่ไม่ผิด ฉันไม่คิดว่าคุณสามารถแก้ไขได้โดยไม่ต้องเก็บวัตถุสองชุดด้วยปุ่มต่าง ๆ ... หรือใช้การแจกแจงแบบ CloudFront สองแบบ (ชื่อโฮสต์ที่แตกต่างกัน) เพื่อที่คุณจะไม่ได้ทำการร้องขอทั้ง CORS และ non-CORS ฉันจะเขียนถึงรายละเอียดว่าฉันมาถึงข้อสรุปนี้ได้อย่างไรถ้าคุณชอบ
Michael - sqlbot

คำตอบ:


55

คุณกำลังร้องขอสองคำขอสำหรับวัตถุเดียวกันหนึ่งคำขอจาก HTML หนึ่งคำขอจาก XHR ข้อที่สองล้มเหลวเนื่องจาก Chrome ใช้การตอบกลับที่แคชจากคำขอแรกซึ่งไม่มีAccess-Control-Allow-Originส่วนหัวการตอบสนอง

ทำไม?

Chromium bug 409090 คำขอข้ามแหล่งกำเนิดจากความล้มเหลวของแคชหลังจากคำขอแคชปกติอธิบายถึงปัญหานี้และเป็น "ไม่แก้ไข" - พวกเขาเชื่อว่าพฤติกรรมของพวกเขาถูกต้อง Chrome พิจารณาว่าการตอบกลับที่แคชไว้นั้นสามารถใช้งานได้อย่างชัดเจนเนื่องจากการตอบกลับนั้นไม่มีVary: Originส่วนหัว

แต่ S3 จะไม่ส่งคืนVary: Originเมื่อมีการร้องขอวัตถุโดยไม่มีOrigin:ส่วนหัวคำขอแม้ว่าจะมีการกำหนดค่า CORS ไว้ในที่เก็บข้อมูลก็ตาม Vary: Originจะถูกส่งเฉพาะเมื่อมีOriginส่วนหัวในคำขอ

และ CloudFront ไม่ได้เพิ่มVary: Originแม้Originจะอยู่ในรายการที่อนุญาตสำหรับการส่งต่อซึ่งตามคำนิยามหมายความว่าการเปลี่ยนแปลงส่วนหัวอาจแก้ไขการตอบสนองนั่นคือเหตุผลที่คุณส่งต่อและแคชกับส่วนหัวของคำขอ

CloudFront ได้รับการผ่านเนื่องจากการตอบสนองของมันจะถูกต้องหาก S3 นั้นถูกต้องมากขึ้นเนื่องจาก CloudFront จะส่งคืนสิ่งนี้เมื่อให้บริการโดย S3

S3, เลือนเล็กน้อย มันไม่ผิดที่จะกลับมาVary: Some-Headerเมื่อไม่มีSome-Headerในคำขอ

ตัวอย่างเช่นคำตอบที่มี

Vary: accept-encoding, accept-language

บ่งชี้ว่าเซิร์ฟเวอร์ต้นทางอาจใช้คำร้องขอ Accept-EncodingและAccept-Languageฟิลด์(หรือไม่มีดังกล่าว)เป็นการพิจารณาปัจจัยในขณะที่เลือกเนื้อหาสำหรับการตอบสนองนี้ (เน้นเพิ่ม)

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

เห็นได้ชัดว่าVary: Some-Absent-Headerถูกต้องดังนั้น S3 จะถูกต้องถ้ามันเพิ่มลงVary: Originในการตอบสนองถ้า CORS มีการกำหนดค่าเนื่องจากที่จริงอาจแตกต่างกันไปการตอบสนอง

และแน่นอนว่านี่จะทำให้ Chrome ทำในสิ่งที่ถูกต้อง MUST NOTหรือถ้ามันไม่ได้ทำสิ่งที่ถูกต้องในกรณีนี้ก็จะละเมิด จากส่วนเดียวกัน:

เซิร์ฟเวอร์ต้นทางอาจส่งVaryพร้อมรายการเขตข้อมูลเพื่อวัตถุประสงค์สองประการ:

  1. เพื่อแจ้งให้ผู้รับแคชทราบว่าพวกเขาMUST NOTใช้การตอบสนองนี้เพื่อตอบสนองคำขอในภายหลังเว้นแต่คำขอในภายหลังจะมีค่าเดียวกันสำหรับเขตข้อมูลที่ระบุไว้ตามคำขอเดิม (ส่วน 4.1 ของ [RFC7234]) กล่าวอีกนัยหนึ่ง Vary จะขยายคีย์แคชที่ต้องการเพื่อจับคู่คำขอใหม่กับรายการแคชที่เก็บไว้

...

ดังนั้น S3 SHOULDจะกลับมาจริงๆVary: Originเมื่อกำหนดค่า CORS ไว้ในที่เก็บข้อมูลหากOriginไม่อยู่ในคำขอ แต่ไม่ได้รับการแก้ไข

ยังคง S3 ไม่เคร่งครัดผิดไม่กลับหัวเพราะมันเป็นเพียงไม่SHOULD MUSTอีกครั้งจากส่วนเดียวกันของ RFC-7231:

เซิร์ฟเวอร์ต้นทางSHOULDส่งฟิลด์ส่วนหัวแตกต่างกันเมื่ออัลกอริทึมสำหรับเลือกการแสดงแตกต่างกันไปตามลักษณะของข้อความคำขอนอกเหนือจากวิธีและเป้าหมายคำขอ ...

ในทางกลับกันอาร์กิวเมนต์อาจทำให้ Chrome ควรรู้ว่าการเปลี่ยนแปลงOriginส่วนหัวควรเป็นแคชคีย์เนื่องจากสามารถเปลี่ยนการตอบสนองในลักษณะเดียวกับที่Authorizationอาจเปลี่ยนการตอบสนอง

... เว้นแต่ความแปรปรวนไม่สามารถข้ามได้หรือเซิร์ฟเวอร์ต้นทางได้รับการกำหนดค่าโดยเจตนาเพื่อป้องกันความโปร่งใสของแคช ตัวอย่างเช่นไม่จำเป็นต้องส่งAuthorizationชื่อฟิลด์Varyเนื่องจากการใช้ซ้ำระหว่างผู้ใช้ถูก จำกัด โดยคำจำกัดความของฟิลด์ [... ]

ในทำนองเดียวกันการนำมาใช้ซ้ำในต้นกำเนิดนั้นถูก จำกัด โดยธรรมชาติOriginแต่การโต้แย้งนี้ไม่ได้เป็นที่แข็งแกร่ง


tl; dr:เห็นได้ชัดว่าคุณไม่สามารถดึงวัตถุจาก HTML ได้สำเร็จจากนั้นดึงข้อมูลอีกครั้งด้วยการร้องขอ CORS ด้วย Chrome และ S3 (มีหรือไม่มี CloudFront) เนื่องจากลักษณะเฉพาะในการนำไปใช้งาน


การแก้ปัญหา:

พฤติกรรมนี้สามารถแก้ไขได้ด้วย CloudFront และ Lambda @ Edge โดยใช้รหัสต่อไปนี้เป็นทริกเกอร์ Origin Response

สิ่งนี้จะเพิ่ม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 ซึ่งมีการแชร์รหัสนี้ในตอนแรก


โซลูชัน Lambda @ Edge ด้านบนส่งผลให้เกิดพฤติกรรมที่ถูกต้อง แต่นี่เป็นทางเลือกสองทางที่คุณอาจพบว่ามีประโยชน์ขึ้นอยู่กับความต้องการเฉพาะของคุณ:

Alternative / Hackaround # 1: ปลอมแปลงส่วนหัว CORS ใน CloudFront

CloudFront สนับสนุนส่วนหัวที่กำหนดเองที่เพิ่มเข้าไปในแต่ละคำขอ หากคุณตั้งค่าOrigin:ตามคำขอทุกครั้งแม้จะเป็นคำขอที่ไม่ข้ามก็ตามสิ่งนี้จะเปิดใช้งานการทำงานที่ถูกต้องใน S3 ตัวเลือกการกำหนดค่าเรียกว่า Custom Origin Headers โดยมีคำว่า "Origin" หมายถึงบางสิ่งที่แตกต่างไปจาก 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

หากคุณใช้คำแนะนำทางเลือกเหล่านี้ให้ใช้อย่างใดอย่างหนึ่ง - ไม่ใช่ทั้งสองอย่าง


5
การตอบสนองของคุณคือผู้ช่วยชีวิตคำตอบที่ดี คุณช่วยฉันเวลาที่ร้ายแรง
mtyurt

สวัสดีฉันไม่ใช้ cloudfront สำหรับ s3 ของฉันดังนั้นวิธีแก้ปัญหานี้ไม่ได้ช่วยอะไรอีกบ้างที่ฉันสามารถทำได้?
Jeffin

1
@Jeffin ทางเลือก # 2 ด้านบนจะใช้งานได้สำหรับ S3 เพียงอย่างเดียวโดยไม่มี CloudFront การเพิ่ม?x-some-key=some-valueพารามิเตอร์สตริงการสืบค้นโดยพลการจะทำให้เบราว์เซอร์มั่นใจว่าคำขอจะแตกต่างกัน
Michael - sqlbot

1
@ Michael-sqlbot: อ๋อทำงานเหมือนมีเสน่ห์
Jeffin

1
@ Lionel ใช่ว่าถูกต้องแล้ว
Michael - sqlbot

1

ฉันไม่รู้ว่าทำไมคุณถึงได้รับผลลัพธ์ที่แตกต่างจากเบราว์เซอร์ต่างๆ แต่:

X-Amz-Cf-Id: wxn_m9meR6yPoyyvj1R7x83pBDPJy1nT7kdMv1aMwXVtHCunT9OC9g ==

บรรทัดนั้นนั่นคือสิ่งที่ (ถ้าคุณได้รับความสนใจ) วิศวกร CloudFront หรือฝ่ายสนับสนุนจะใช้ในการติดตามคำขอที่ล้มเหลวของคุณ หากคำขอกำลังจะไปยังเซิร์ฟเวอร์ CloudFront ควรมีส่วนหัวนี้ในการตอบกลับ หากส่วนหัวนั้นไม่อยู่ที่นั่นแสดงว่าคำขอนั้นอาจล้มเหลวก่อนที่จะไปถึง CloudFront


ขอบคุณฉันจะดูว่าฉันจะได้รับคำตอบใด ๆ ในฟอรัม AWS หรือไม่
SunSparc

1
คุณอาจต้องจ่าย $ 29 สำหรับการสนับสนุนนักพัฒนาซอฟต์แวร์ นั่นเป็นจำนวนเงินที่ไม่สำคัญสำหรับธุรกิจใด ๆ เนื่องจากค่าใช้จ่ายด้านเวลา
ทิม

1
@Tim โปรดทราบว่าการสนับสนุนนักพัฒนาไม่ใช่เพียงแค่ $ 29 นั่นคือราคาฐาน หาก 3% ของการเรียกเก็บเงิน AWS รายเดือนของคุณคือ> = $ 29 คุณจะจ่าย 3% แทนค่าฐาน
Michael - sqlbot

ขอบคุณ @ Michael-sqlbot ฉันไม่ได้ตระหนักถึงสิ่งนั้น ฉันรู้ว่าราคาสนับสนุนสามารถเพิ่มขึ้นอย่างรวดเร็วเมื่อคุณมีสิ่งต่าง ๆ เช่นอินสแตนซ์ที่สงวนไว้ แต่ฉันไม่เคยดูการกำหนดราคาของนักพัฒนาซอฟต์แวร์เมื่อคุณมีทรัพยากรจำนวนมาก
ทิม
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.