web-dev-qa-db-ja.com

Chrome S3 Cloudfront:最初のXHRリクエストに「Access-Control-Allow-Origin」ヘッダーがありません

CloudFront CDNを介してS3からいくつかのSVGファイルをロードするためにjQueryを使用するウェブページ( 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エラーなしでロードされます。

場合によっては、画像が読み込まれた後でも更新され、1つ以上の画像が読み込まれず、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側に問題がないことを示しているようです。それ以外の場合、コンテンツが配信されるのはなぜですか。しかし、なぜコンテンツが最初のページビューで配信されないのでしょうか?

私は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
34
SunSparc

同じオブジェクトに対して2つのリクエストを作成します。1つはHTMLから、もう1つはXHRからです。 ChromeはAccess-Control-Allow-Origin応答ヘッダーのない最初の要求からのキャッシュされた応答を使用するため、2番目の方法は失敗します。

どうして?

Chromiumバグ409090キャッシュからのクロスオリジンリクエストは、通常のリクエストがキャッシュされた後に失敗します この問題を説明し、「修正されません」-彼らの行動は正しいと信じています。 Chromeは、キャッシュされた応答が使用可能であると見なします、明らかに応答にVary: Originが含まれていないためヘッダ。

ただし、バケットにCORSが設定されている場合でも、Vary: Originリクエストヘッダーなしでオブジェクトがリクエストされると、S3はOrigin:を返しません。 Vary: Originは、リクエストにOriginヘッダーが存在する場合にのみ送信されます。

また、CloudFrontは、Originが転送用にホワイトリストに登録されている場合でもVary: 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は有効であるため、CORSが設定されている場合、S3がVary: Originを応答に追加した場合、S3は正しいはずです。

そして、明らかに、これはChrome=が正しいことを行うようにします。または、この場合に正しいことを行わない場合は、MUST NOTに違反しています。同じセクション:

オリジンサーバーは、次の2つの目的で、フィールドのリストとともにVaryを送信する場合があります。

  1. キャッシュの受信者にMUST NOTであることを通知するには、リストされたフィールドに元のリクエストと同じ値が後のリクエストにない限り([RFC7234]のセクション4.1)、このレスポンスを使用して後のリクエストを満たします。つまり、Varyは、新しい要求を格納されたキャッシュエントリに一致させるために必要なキャッシュキーを拡張します。

...

そのため、リクエストにSHOULDが存在しない場合にCORSがバケットに設定されていると、S3は実際にOriginVary: Originを返すことになりますが、そうではありません。

それでも、ヘッダーを返さないことはS3が厳密に間違っているわけではありません。S3はSHOULDではなくMUSTにすぎないためです。繰り返しますが、RFC-7231の同じセクションから:

オリジンサーバーSHOULDは、メソッドとリクエストターゲット以外のリクエストメッセージの側面に基づいて表現を選択するアルゴリズムが異なる場合、Varyヘッダーフィールドを送信します。

一方、Chromeは、Originがレスポンスを変更するのと同じ方法でレスポンスを変更できるため、Authorizationヘッダーの変更がキャッシュキーであることを暗黙的に認識している必要があります。

...バリアンスを超えることができないか、オリジンサーバーがキャッシュの透過性を防ぐように意図的に設定されている場合を除きます。たとえば、Authorizationフィールド名をVaryで送信する必要はありません。これは、ユーザー間での再利用がフィールド定義によって制限されているためです[...]

同様に、起点間の再利用は、Originの性質によって間違いなく制約されますが、この引数は強力なものではありません。


tl; dr:HTMLからオブジェクトを正常にフェッチできず、ChromeおよびS3(CloudFrontの有無にかかわらず)、実装の特殊性のため。


回避策:

この動作は、次のコードをOrigin Responseトリガーとして使用して、CloudFrontおよびLambda @ Edgeで回避できます。

これにより、Vary: Access-Control-Request-Headers, Access-Control-Request-Method, OriginVaryヘッダーを持たないS3からの応答に追加されます。それ以外の場合、応答の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ソリューションは完全に正しい動作をもたらしますが、特定のニーズに応じて、次の2つの代替案が役立つ場合があります。

代替/ハックアラウンド#1:CloudFrontでCORSヘッダーを偽造します。

CloudFrontは、各リクエストに追加されるカスタムヘッダーをサポートします。すべてのリクエストでOrigin:を設定すると、クロスオリジンでないリクエストでも、これによりS3での正しい動作が可能になります。構成オプションはカスタムオリジンヘッダーと呼ばれ、「Origin」という単語はCORSとはまったく異なるものを意味します。 CloudFrontでこのようなカスタムヘッダーを構成すると、リクエストで送信されたものが指定された値で上書きされるか、存在しない場合は追加されます。 正確に1つのOriginが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コンテキストで再度要求する必要のある個別のオブジェクトであることがわかります。

これらの代替案を使用する場合は、どちらか一方を使用してください。両方は使用しないでください。

64

さまざまなブラウザーからこのような異なる結果が得られる理由はわかりませんが、

X-Amz-Cf-Id:wxn_m9meR6yPoyyvj1R7x83pBDPJy1nT7kdMv1aMwXVtHCunT9OC9g ==

その行は、CloudFrontまたはサポートエンジニアが失敗したリクエストの1つを追跡するために(注意を引くことができる場合)何を使用するかを示しています。リクエストがCloudFrontサーバーに到達している場合は、応答にこのヘッダーが含まれている必要があります。そのヘッダーがない場合、リクエストはCloudFrontに到達する前にどこかで失敗している可能性があります。

1
unixguy