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


30

jQueryを使用してS3からCloudFront CDNを介していくつかのSVGファイルをロードするWebページ(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側にあることを示しているようです。しかし、なぜ最初のページビューでコンテンツが配信されないのでしょうか?

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

あなたは正しいです-削除と再作成は極端であり、決して必要ではないはずです。失敗したリクエストに対するブラウザのリクエストとレスポンスのヘッダーを表示できますか?そしておそらく、まったく同じオブジェクトのリクエストが成功したのでしょうか?
マイケル-sqlbot

@ Michael-sqlbot、URL(smartystreets.com/contact)にアクセスして、同じことがマシンで発生しているかどうかを確認することを望んでいました。:)エラーの興味深い点は、コンソールのエラーを除いて、ブラウザが200のステータスを報告することです。これは、シークレットでは不可能な「(ディスクキャッシュから)」イメージを使用しているためです。思想。ローカルキャッシュをクリアした後でも。
SunSparc

1
ええ、人々は頻繁にドメイン名(実際のサイトであることが判明しますが、問題のサイトではありません)を「構成」しているので、最初はあなたがあなたのサイトへの実際の正しいリンクを与えたことに気づきませんでした。そのおかげで、あなたは私の要求を無視することができます。問題を再現できます。これはクライアント側の問題のようです。私は理論を追いかけています。
マイケル-sqlbot

それがクライアント側の問題であることについてあなたは正しいのではないかと思います。画像はHTMLのAタグでリンクされており、jQueryで再びリクエストされているように見えます。おそらく、エラーは1つの呼び出しからのものであり、200は他の呼び出しからのものです。
SunSparc

1
それはまさに私がそうだと信じていることです。ChromeとS3は、同じオブジェクトに対する非CORS要求に続くCORS要求を中断する方法で相互作用しています。間違いなく、両方とも間違っています...しかし、間違いなく、どちらも間違っていません。オブジェクトの2つのコピーを異なるキーで保存することなく、または2つの異なるCloudFrontディストリビューション(異なるホスト名)を使用せずにこれを修正できるとは思わないので、CORSリクエストと非CORSリクエストの両方を作成しません。必要に応じて、この結論にどのようにたどり着いたかを詳しく説明します。
マイケル-sqlbot

回答:


55

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

どうして?

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

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

また、CloudFrontは、転送用のホワイトリストに登録されVary: Originている場合でも追加しませんOrigin。これは、定義によりヘッダーを変更すると応答が変更される可能性があることを意味します。

CloudFrontはパスを取得します。これは、S3が提供したときにCloudFrontがこれを返すため、S3の方がより正確であれば応答が正しいためです。

S3、少しファジー。間違っていない返さないためにVary: Some-Header何があったときSome-Header、要求に。

たとえば、次を含む応答

Vary: accept-encoding, accept-language

この応答のコンテンツを選択する際に、オリジンサーバが要求Accept-EncodingAccept-Languageフィールド(またはその欠如)を決定要因として使用した可能性があることを示します 。(強調を追加)

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

明らかに、Vary: Some-Absent-Header有効であるためVary: Origin、CORSが設定されている場合、応答に実際に変化する可能性があるため、S3が応答に追加された場合、S3は正しいでしょう。

そして、明らかに、これはChromeに正しいことをさせるでしょう。または、この場合に正しいことをしないと、に違反することになりますMUST NOT。同じセクションから:

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

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

...

そのため、バケットにCORSが設定されている場合、リクエストにSRS が存在しない場合、S3は本当にSHOULD戻りVary: Originますが、そうでOriginはありません。

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

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

一方、Chromeは、Originヘッダーを変更するとキャッシュキーにする必要があることを暗黙的に知っている必要があると主張することができますAuthorization

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

同様に、オリジン間での再利用は、その性質によってほぼ制限されていますがOrigin、この議論は強力なものではありません。


tl; dr:実装の特性により、HTMLからオブジェクトを正常に取得してから、ChromeおよびS3(CloudFrontの有無にかかわらず)を使用してCORSリクエストで再度正常に取得することはできないようです。


回避策:

この動作は、次のコードを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でこのようなカスタムヘッダーを設定すると、リクエストで送信されたものが指定された値で上書きされるか、存在しない場合は追加されます。たとえば、XHRを介してコンテンツにアクセスするオリジンが1つだけの場合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コンテキストで再度要求する必要があると確信します。

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


5
あなたの応答は命の恩人、素晴らしい答えです。大変な時間を節約してくれました。
mtyurt

こんにちは、s3にcloudfrontを使用しないので、この回避策は役に立たないのですが、他にできることはありますか?
ジェフィン

1
@Jeffin、上記の代替#2は、CloudFrontなしでS3のみで機能します。任意の?x-some-key=some-valueクエリ文字列パラメータを追加すると、リクエストが異なることをブラウザに確信させます。
マイケル-sqlbot

1
@ Michael-sqlbot:うん、魔法のように働いた
ジェフィン

1
@Lionelはい、それは正しいようです。
マイケル-sqlbot

1

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

X-Amz-Cf-Id:wxn_m9meR6yPoyyvj1R7x83pBDPJy1nT7kdMv1aMwXVtHCunT9OC9g ==

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


おかげで、AWSフォーラムで回答が得られるかどうかを確認できます。
SunSparc

1
開発者サポートのために29ドルを支払う必要があるかもしれません。これは、人がどれだけ時間がかかるかを考えると、どんなビジネスにとっても些細な金額です。
ティム

1
@Tim、開発者サポートは単に29ドルではないことに注意してください。それが基本価格です。毎月のAWS請求書の3%が29ドル以上の場合、ベースの代わりに3%を支払います。
マイケル-sqlbot

@ Michael-sqlbotのおかげで、私はそれを知りませんでした。リザーブドインスタンスのようなものがある場合、サポート価格はすぐに加算されますが、多くのリソースがある場合、開発者の価格を検討したことはありません。
ティム
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.