Chrome S3 Cloudfront: Không có tiêu đề 'Kiểm soát truy cập-Cho phép-Xuất xứ' trong yêu cầu XHR ban đầu


30

Tôi có một trang web ( https://smartystreets.com/contact ) sử dụng jQuery để tải một số tệp SVG từ S3 thông qua CDN của CloudFront.

Trong Chrome tôi sẽ mở một cửa sổ Ẩn danh cũng như bảng điều khiển. Sau đó tôi sẽ tải trang. Khi tải trang, tôi thường sẽ nhận được 6 đến 8 tin nhắn trong bảng điều khiển trông giống như thế này:

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.

Nếu tôi thực hiện tải lại tiêu chuẩn của trang, thậm chí nhiều lần, tôi tiếp tục gặp các lỗi tương tự. Nếu tôi làm Command+Shift+Rthì hầu hết, và đôi khi tất cả, các hình ảnh sẽ tải mà không có XMLHttpRequestlỗi.

Đôi khi ngay cả sau khi hình ảnh được tải, tôi sẽ làm mới và một hoặc nhiều hình ảnh sẽ không tải và trả lại XMLHttpRequestlỗi đó .

Tôi đã kiểm tra, thay đổi và kiểm tra lại các cài đặt trên S3 và Cloudfront. Trong S3, cấu hình CORS của tôi trông như thế này:

<?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>

(Lưu ý: ban đầu chỉ có <AllowedOrigin>*</AllowedOrigin>, cùng một vấn đề.)

Trong CloudFront, hành vi phân phối được đặt để cho phép Phương thức HTTP : GET, HEAD, OPTIONS. Các phương thức lưu trữ là như nhau. Tiêu đề chuyển tiếp được đặt thành "Danh sách trắng" và danh sách trắng đó bao gồm, "Tiêu đề kiểm soát truy cập-yêu cầu, tiêu đề kiểm soát truy cập, yêu cầu, nguồn gốc".

Thực tế là nó hoạt động sau khi tải lại trình duyệt không có bộ nhớ cache dường như cho thấy tất cả đều ổn ở phía S3 / CloudFront, ngoài ra tại sao nội dung sẽ được phân phối. Nhưng tại sao nội dung sẽ không được phân phối trên lượt xem trang ban đầu?

Tôi đang làm việc trong Google Chrome trên macOS. Firefox không có vấn đề gì khi nhận các tệp mỗi lần. Opera KHÔNG BAO GIỜ được các tập tin. Safari sẽ chọn hình ảnh sau vài lần làm mới.

Sử dụng curltôi không gặp vấn đề gì:

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==

Một số người đã gợi ý rằng tôi xóa bản phân phối CloudFront và tạo lại nó. Có vẻ như một sửa chữa khá khắc nghiệt và bất tiện.

Điều gì gây ra vấn đề này?

Cập nhật:

Thêm tiêu đề phản hồi từ một hình ảnh không tải được.

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

Bạn đã đúng - xóa và tạo lại là cực đoan và đơn giản là không bao giờ cần thiết. Bạn có thể chỉ cho chúng tôi yêu cầu và tiêu đề phản hồi của trình duyệt cho một yêu cầu không? Và có thể cho một yêu cầu thành công của cùng một đối tượng?
Michael - sqlbot

@ Michael-sqlbot, tôi rất hy vọng bạn sẽ truy cập URL ( smartystreets.com/contact ) và xem điều tương tự có xảy ra trên máy của bạn không. :) Điều thú vị về các lỗi là ngoài lỗi trong bảng điều khiển, trình duyệt báo cáo trạng thái 200, với lý do nó đang sử dụng hình ảnh "(từ bộ đệm đĩa)", không thể có với Incognito, I nghĩ. Ngay cả sau khi tôi xóa bộ nhớ cache cục bộ.
SunSparc

1
Vâng, mọi người thường "tạo nên" tên miền (hóa ra đó là các trang web thực sự, nhưng không phải là trang web được đề cập) mà ban đầu tôi không nhận ra rằng bạn đã đưa ra liên kết chính xác, thực sự đến trang web của mình. Cảm ơn vì điều đó, bạn có thể bỏ qua yêu cầu của tôi. Tôi có thể nhân đôi vấn đề. Điều này có vẻ như một vấn đề phía khách hàng. Tôi đang theo đuổi một lý thuyết.
Michael - sqlbot

Tôi nghĩ rằng bạn có thể đúng về nó là một vấn đề phía khách hàng. Các hình ảnh được liên kết với các thẻ A trong HTML và sau đó có vẻ như chúng được yêu cầu lại trong jQuery. Có lẽ lỗi là từ một cuộc gọi và 200 là từ cuộc gọi khác.
SunSparc

1
Đó là chính xác những gì tôi tin là trường hợp. Chrome và S3 đang tương tác theo cách phá vỡ yêu cầu CORS theo yêu cầu không phải CORS cho cùng một đối tượng. Có thể cho rằng, cả hai đều sai ... nhưng có thể cho rằng, cả hai đều không sai. Tôi không nghĩ bạn có thể sửa lỗi này mà không lưu trữ hai bản sao của đối tượng bằng các khóa khác nhau ... hoặc sử dụng hai bản phân phối CloudFront khác nhau (tên máy chủ khác nhau) để bạn không thực hiện cả yêu cầu CORS và không CORS. Tôi sẽ viết nó với các chi tiết về cách tôi đi đến kết luận này, nếu bạn muốn.
Michael - sqlbot

Câu trả lời:


55

Bạn đang thực hiện hai yêu cầu cho cùng một đối tượng, một từ HTML, một từ XHR. Cái thứ hai không thành công, vì Chrome sử dụng phản hồi được lưu trong bộ nhớ cache từ yêu cầu đầu tiên, không có Access-Control-Allow-Origintiêu đề phản hồi.

Tại sao?

Lỗi Chromium 409090 Yêu cầu nguồn gốc chéo từ bộ đệm bị lỗi sau khi yêu cầu thông thường được lưu trong bộ nhớ cache mô tả vấn đề này và đó là "sẽ không khắc phục" - họ tin rằng hành vi của họ là chính xác. Chrome coi phản hồi được lưu trong bộ nhớ cache là có thể sử dụng được, rõ ràng vì phản hồi không bao gồm Vary: Origintiêu đề.

Nhưng S3 không trả về Vary: Originkhi một đối tượng được yêu cầu mà không có Origin:tiêu đề yêu cầu, ngay cả khi CORS được cấu hình trên nhóm. Vary: Originchỉ được gửi khi một Origintiêu đề có mặt trong yêu cầu.

Và CloudFront không thêm Vary: Originngay cả khi Originđược đưa vào danh sách trắng để chuyển tiếp, theo định nghĩa, điều đó có nghĩa là việc thay đổi tiêu đề có thể sửa đổi phản hồi - đó là lý do tại sao bạn chuyển tiếp và lưu vào bộ đệm theo yêu cầu.

CloudFront được thông qua, vì phản hồi của nó sẽ đúng nếu S3 đúng hơn, vì CloudFront sẽ trả lại điều này khi được S3 cung cấp.

S3, một chút mờ hơn. Sẽ không saiVary: Some-Header khi trả lại khi không có Some-Headeryêu cầu.

Ví dụ: một phản hồi có chứa

Vary: accept-encoding, accept-language

chỉ ra rằng máy chủ gốc có thể đã sử dụng các yêu cầu Accept-EncodingAccept-Languagecác trường (hoặc thiếu chúng) để xác định các yếu tố trong khi chọn nội dung cho phản hồi này. (nhấn mạnh thêm)

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

Rõ ràng, Vary: Some-Absent-Headerlà hợp lệ, vì vậy S3 sẽ chính xác nếu được thêm vào Vary: Originphản hồi của nó nếu CORS được cấu hình, vì điều đó thực sự có thể thay đổi phản hồi.

Và rõ ràng, điều này sẽ khiến Chrome làm điều đúng đắn. Hoặc, nếu nó không làm đúng trong trường hợp này, nó sẽ vi phạm a MUST NOT. Từ cùng một phần:

Một máy chủ gốc có thể gửi Varyvới một danh sách các trường cho hai mục đích:

  1. Để thông báo cho người nhận bộ đệm rằng họ MUST NOTsử dụng phản hồi này để đáp ứng yêu cầu sau trừ khi yêu cầu sau đó có cùng giá trị cho các trường được liệt kê như yêu cầu ban đầu (Mục 4.1 của [RFC7234]). Nói cách khác, Vary mở rộng khóa bộ đệm cần thiết để khớp một yêu cầu mới với mục bộ nhớ cache được lưu trữ.

...

Vì vậy, S3 thực sự SHOULDquay trở lại Vary: Originkhi CORS được cấu hình trên nhóm, nếu Originkhông có yêu cầu, nhưng không được.

Tuy nhiên, S3 không hoàn toàn sai khi không trả lại tiêu đề, bởi vì đó chỉ là a SHOULD, không phải a MUST. Một lần nữa, từ cùng một phần của RFC-7231:

Máy chủ gốc SHOULDgửi trường tiêu đề Vary khi thuật toán chọn biểu diễn thay đổi dựa trên các khía cạnh của thông báo yêu cầu khác với phương thức và mục tiêu yêu cầu, ...

Mặt khác, có thể đưa ra lập luận rằng Chrome nên ngầm biết rằng việc thay đổi Origintiêu đề phải là khóa bộ đệm vì nó có thể thay đổi phản hồi theo cùng một cách Authorizationcó thể thay đổi phản hồi.

... trừ khi phương sai không thể được vượt qua hoặc máy chủ gốc đã được cấu hình có chủ ý để ngăn chặn độ trong suốt của bộ đệm. Ví dụ: không cần gửi Authorizationtên trường Varyvì việc sử dụng lại giữa các người dùng bị hạn chế bởi định nghĩa trường [...]

Tương tự, việc tái sử dụng trên các nguồn gốc được cho là bị hạn chế bởi bản chất của Originnhưng đối số này không phải là một vấn đề mạnh.


tl; dr: Bạn dường như không thể tìm nạp thành công một đối tượng từ HTML và sau đó tìm nạp lại thành công với yêu cầu CORS với Chrome và S3 (có hoặc không có CloudFront), do đặc thù trong việc triển khai.


Cách giải quyết:

Hành vi này có thể được xử lý xung quanh với CloudFront và Lambda @ Edge, sử dụng mã sau đây làm trình kích hoạt Phản hồi gốc.

Điều này thêm Vary: Access-Control-Request-Headers, Access-Control-Request-Method, Originvào bất kỳ phản hồi nào từ S3 không có Varytiêu đề. Mặt khác, Varytiêu đề trong phản hồi không được sửa đổi.

'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);
};

Ghi công: Tôi cũng là tác giả của bài đăng gốc trên các diễn đàn Hỗ trợ AWS nơi mã này được chia sẻ ban đầu.


Giải pháp Lambda @ Edge ở trên cho kết quả hoàn toàn chính xác, nhưng đây là hai lựa chọn thay thế mà bạn có thể thấy hữu ích, tùy thuộc vào nhu cầu cụ thể của bạn:

Thay thế / Hackaround # 1: Tạo các tiêu đề CORS trong CloudFront.

CloudFront hỗ trợ các tiêu đề tùy chỉnh được thêm vào mỗi yêu cầu. Nếu bạn đặt Origin:theo mọi yêu cầu, ngay cả những yêu cầu không có nguồn gốc chéo, điều này sẽ cho phép hành vi chính xác trong S3. Tùy chọn cấu hình được gọi là Tiêu đề xuất xứ tùy chỉnh, với từ "Xuất xứ" có nghĩa là một cái gì đó hoàn toàn khác với nghĩa của nó trong CORS. Định cấu hình tiêu đề tùy chỉnh như thế này trong CloudFront ghi đè lên những gì được gửi trong yêu cầu với giá trị được chỉ định hoặc thêm nó nếu không có. Nếu bạn có chính xác một nguồn gốc truy cập nội dung của bạn qua XHR, ví dụ: https://example.combạn có thể thêm nội dung đó. Sử dụng *là không rõ ràng, nhưng có thể làm việc cho các kịch bản khác. Hãy xem xét các hàm ý cẩn thận.

Thay thế / Hackaround # 2: Sử dụng tham số chuỗi truy vấn "giả" khác với HTML và XHR hoặc không có ở cái này hay cái kia. Các tham số này thường được đặt tên x-*nhưng không nên x-amz-*.

Hãy nói rằng bạn tạo nên tên x-request. Vì vậy <img src="https://dzczcexample.cloudfront.net/image.png?x-request=html">. Khi truy cập đối tượng từ JS, không thêm tham số truy vấn. CloudFront đã làm đúng, bằng cách lưu trữ các phiên bản khác nhau của các đối tượng bằng cách sử dụng Origintiêu đề hoặc không có nó như một phần của khóa bộ đệm, bởi vì bạn đã chuyển tiếp tiêu đề đó trong hành vi bộ đệm của mình. Vấn đề là, trình duyệt của bạn không biết điều này. Điều này thuyết phục trình duyệt rằng đây thực sự là một đối tượng riêng biệt cần được yêu cầu lại, trong bối cảnh CORS.

Nếu bạn sử dụng các đề xuất thay thế này, hãy sử dụng cái này hoặc cái kia - không phải cả hai.


5
Phản ứng của bạn là một cứu cánh, câu trả lời tuyệt vời. Bạn đã tiết kiệm cho tôi một thời gian nghiêm túc.
mtyurt

Xin chào, tôi không sử dụng cloudfront cho s3 của mình vì vậy cách giải quyết này không có ích gì, tôi có thể làm gì khác không?
Jeffin

1
@Jeffin, lựa chọn số 2 ở trên sẽ chỉ hoạt động cho S3 mà không có CloudFront. Thêm một ?x-some-key=some-valuetham số chuỗi truy vấn tùy ý sẽ thuyết phục trình duyệt rằng yêu cầu khác nhau.
Michael - sqlbot

1
@ Michael-sqlbot: Đúng, làm việc như một bùa mê
Jeffin

1
@Lionel vâng, điều đó có vẻ đúng.
Michael - sqlbot

1

Tôi không biết tại sao bạn lại nhận được kết quả khác nhau như vậy từ các trình duyệt khác nhau, nhưng:

X-Amz-Cf-Id: wxn_m9meR6yPoyyvj1R7x83pBDPJy1nT7kdMv1aMwXVtHCunT9OC9g ==

Dòng đó có những gì (nếu bạn có thể thu hút sự chú ý của họ), một kỹ sư CloudFront hoặc Hỗ trợ sẽ sử dụng để thực hiện theo một trong những yêu cầu thất bại của bạn. Nếu yêu cầu đến máy chủ CloudFront, yêu cầu sẽ có tiêu đề này trong phản hồi. Nếu tiêu đề đó không có ở đó, thì yêu cầu có thể sẽ thất bại ở đâu đó trước khi nó đến CloudFront.


Cảm ơn, tôi sẽ xem liệu tôi có thể nhận được bất kỳ phản hồi nào trên các diễn đàn AWS không.
SunSparc

1
Bạn có thể cần phải trả $ 29 cho hỗ trợ nhà phát triển. Đó là một khoản tiền không đáng kể cho bất kỳ doanh nghiệp nào, với chi phí thời gian của một người.
Tim

1
@Tim, lưu ý rằng hỗ trợ nhà phát triển không chỉ đơn giản là $ 29. Đó là giá cơ bản. Nếu 3% hóa đơn AWS hàng tháng của bạn> = 29 đô la, bạn phải trả 3% thay vì căn cứ.
Michael - sqlbot

Cảm ơn @ Michael-sqlbot, tôi đã không nhận ra điều đó. Tôi biết giá hỗ trợ có thể tăng lên nhanh chóng khi bạn có những thứ như phiên bản dành riêng, nhưng tôi chưa bao giờ xem giá của nhà phát triển khi bạn có nhiều tài nguyên.
Tim
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.