Chrome S3 Cloudfront : 초기 XHR 요청에 'Access-Control-Allow-Origin'헤더가 없습니다.


30

jQuery를 사용하여 CloudFront CDN을 통해 S3에서 일부 SVG 파일을로드 하는 웹 페이지 ( https://smartystreets.com/contact )가 있습니다.

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 있습니다. 캐시 된 방법은 동일합니다. 전달 헤더는 "화이트리스트"로 설정되고 화이트리스트에는 "액세스 제어 요청 헤더, 액세스 제어 요청 방법, 원본"이 포함됩니다.

캐시가없는 브라우저를 다시로드 한 후에 작동한다는 사실은 모든 것이 S3 / CloudFront 측에 잘 있음을 나타내는 것으로 보입니다. 그렇지 않으면 왜 컨텐츠가 전달됩니까? 그렇다면 왜 콘텐츠가 초기 페이지 뷰에 전달되지 않습니까?

macOS의 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

당신은 옳습니다-삭제와 재생성은 극단적이며 절대로 필요하지 않아야합니다. 실패한 요청에 대한 브라우저 요청 및 응답 헤더를 표시 할 수 있습니까? 그리고 똑같은 물건을 성공적으로 요구할 수 있습니까?
Michael-sqlbot 2016 년

@ Michael-sqlbot, URL ( smartystreets.com/contact )을 방문 하여 컴퓨터에서 동일한 일이 발생했는지 확인하고 싶었습니다 . :) 오류에 대한 흥미로운 점은 콘솔의 오류를 제외하고 브라우저가 200 (상태에서 디스크 캐시) 이미지를 사용하고 있음을 나타내는 200의 상태를보고한다는 것입니다. 시크릿 모드에서는 불가능합니다. 생각. 로컬 캐시를 지운 후에도.
SunSparc 2016 년

1
예, 사람들은 도메인 이름 (실제 사이트로 밝혀졌지만 해당 사이트는 아닌 것으로 밝혀 짐)을 자주 "만들기"때문에 처음에는 사이트에 대한 정확한 링크를 제공 한 사실을 알지 못했습니다. 그것에 대해 감사합니다, 당신은 내 요청을 무시할 수 있습니다. 문제를 복제 할 수 있습니다. 이것은 클라이언트 측 문제처럼 보입니다. 나는 이론을 쫓고있다.
Michael-sqlbot 2016 년

나는 그것이 당신이 클라이언트 측 문제라는 것에 대해 맞을 것이라고 생각합니다. 이미지는 HTML에서 A 태그와 연결되어 있으며 jQuery에서 다시 요청 된 것처럼 보입니다. 아마도 오류는 한 호출에서 발생하고 200은 다른 호출에서 발생했을 수 있습니다.
SunSparc

1
그것이 바로 그 경우라고 생각합니다. Chrome과 S3는 동일한 객체에 대해 CORS 이외의 요청을 따르는 CORS 요청을 중단시키는 방식으로 상호 작용하고 있습니다. 논쟁의 여지가 있지만, 둘 다 틀렸다 ...하지만 논쟁의 여지가 없다. CORS 및 비 CORS 요청을 모두 수행하지 않도록 서로 다른 키가있는 객체의 사본 두 개를 저장하지 않고 또는 두 개의 다른 CloudFront 배포 (다른 호스트 이름)를 사용하지 않고도이 문제를 해결할 수 있다고 생각하지 않습니다. 원하는 경우이 결론에 도달하는 방법에 대한 자세한 내용을 기록하겠습니다.
Michael-sqlbot 2016 년

답변:


55

동일한 객체에 대해 HTML에서 하나, XHR에서 하나의 두 가지 요청을하고 있습니다. Chrome은 Access-Control-Allow-Origin응답 헤더 가없는 첫 번째 요청의 캐시 된 응답을 사용하기 때문에 실패합니다 .

왜?

Chromium bug 409090 정기 요청이 캐시 된 후 캐시 실패에서 발생하는 교차 출처 요청은 이 문제를 설명하며 "수정되지 않습니다"-동작이 올바르다 고 생각합니다. 크롬, 캐시 된 응답이 가능한 것으로 간주 분명히 응답이 포함되지 않았기 때문에 Vary: Origin헤더를.

그러나 CORS가 버킷에 구성된 경우에도 요청 헤더 Vary: Origin없이 객체를 요청하면 S3가 반환되지 않습니다 Origin:. 요청에 헤더가있는 Vary: Origin경우에만 전송됩니다 Origin.

또한 전달을 위해 화이트리스트에 등록 된 Vary: Origin경우에도 CloudFront는 추가되지 않습니다. 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: OriginCORS가 구성된 경우 S3가 응답에 추가 되면 응답이 달라질 수 있으므로 S3이 올바른 것 입니다.

그리고 분명히 이것은 Chrome이 올바른 일을하게합니다. 또는이 경우 올바른 작업을 수행하지 않으면를 위반하는 것 MUST NOT입니다. 같은 섹션에서 :

오리진 서버는 Vary두 가지 목적으로 필드 목록과 함께 보낼 수 있습니다 .

  1. MUST NOT이후 요청이 원래 요청과 동일한 필드 값을 갖지 않는 한이 요청을 이후 요청을 만족시키기 위해 캐시 응답자 에게 알리기 위해 ([RFC7234] 4.1 절). 즉, Vary는 새 요청을 저장된 캐시 항목과 일치시키는 데 필요한 캐시 키를 확장합니다.

...

따라서 요청에 빠지지 않으면 버킷에 CORS가 구성되면 S3이 실제로 SHOULD반환 되지만 그렇지 않습니다.Vary: OriginOrigin

그럼에도 불구하고 S3는 헤더가 반환되지 않은 것으로 엄격하게 잘못 SHOULD되지 않습니다 MUST. RFC-7231의 동일한 섹션에서 다시 :

오리진 서버 SHOULD는 표현을 선택하기위한 알고리즘이 메소드 및 요청 대상 이외의 요청 메시지의 측면에 따라 달라질 때 Vary 헤더 필드를 보냅니다.

반면에 크롬은 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 솔루션은 완전히 올바르게 동작하지만 특정 요구에 따라 유용한 두 가지 대안이 있습니다.

대안 / 주변 # 1 : CloudFront에서 CORS 헤더를 위조하십시오.

CloudFront는 각 요청에 추가되는 사용자 지정 헤더를 지원합니다. Origin:교차 요청이 아닌 요청을 포함하여 모든 요청 을 설정하면 S3에서 올바른 동작이 가능합니다. 구성 옵션을 Custom Origin Headers라고하며, "Origin"이라는 단어는 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에서 객체에 액세스 할 때 쿼리 매개 변수를 추가하지 마십시오. Origin캐시 동작에서 해당 헤더를 전달 했으므로 CloudFront는 헤더를 캐시 키의 일부로 사용 하거나 존재하지 않는 다른 버전의 객체를 캐싱하여 이미 올바른 작업을 수행하고 있습니다. 문제는 브라우저가 이것을 모른다는 것입니다. 이것은 브라우저에게 이것이 실제로 CORS 컨텍스트에서 다시 요청해야하는 별도의 객체임을 확신시킵니다.

이러한 대체 제안을 사용하는 경우 둘 중 하나만 사용하십시오.


5
당신의 응답은 생명의 은인, 훌륭한 답변입니다. 당신은 나에게 심각한 시간을 절약했다.
mtyurt

안녕하세요, 내 s3에 cloudfront를 사용하지 않으므로이 해결 방법으로 도움이되지 않습니다. 다른 방법이 있습니까?
Jeffin

1
@Jeffin, 위의 대안 # 2는 CloudFront없이 S3에서만 작동합니다. 임의의 ?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 2016 년

1
개발자 지원을 위해 $ 29를 지불해야 할 수도 있습니다. 그것은 시간이 얼마나 많은 사람을 고려할 때, 모든 사업에 대한 사소한 금액입니다.
Tim

1
@Tim, 개발자 지원은 단순히 $ 29가 아니라는 점에 유의하십시오. 기본 가격입니다. 월별 AWS 청구서의 3 %가 29 달러를 초과하는 경우 기본 금액 대신 3 %를 지불합니다.
Michael-sqlbot

@ Michael-sqlbot에게 감사드립니다. 예약 인스턴스와 같은 것이 있으면 지원 가격이 빠르게 증가 할 수 있지만 리소스가 많을 때 개발자 가격을 본 적이 없습니다.
Tim
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.