Chrome S3 Cloudfront: Bei der ersten XHR-Anforderung wird kein Header "Access-Control-Allow-Origin" angezeigt

30

Ich habe eine Webseite ( https://smartystreets.com/contact ), die jQuery verwendet, um einige SVG-Dateien von S3 über das CloudFront-CDN zu laden.

In Chrome öffne ich ein Inkognito-Fenster sowie die Konsole. Dann lade ich die Seite. Beim Laden der Seite werden in der Regel 6 bis 8 Meldungen in der Konsole angezeigt, die ungefähr so ​​aussehen:

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.

Wenn ich die Seite mehrmals standardmäßig neu lade, werden weiterhin dieselben Fehler angezeigt. Wenn ich das tue, werden die Command+Shift+Rmeisten und manchmal alle Bilder ohne XMLHttpRequestFehler geladen .

Manchmal aktualisiere ich sogar nach dem Laden der Bilder, und eines oder mehrere der Bilder werden nicht geladen und geben diesen XMLHttpRequestFehler erneut zurück.

Ich habe die Einstellungen in S3 und Cloudfront überprüft, geändert und erneut überprüft. In S3 sieht meine CORS Konfiguration so aus:

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

(Hinweis: hatte anfangs nur das <AllowedOrigin>*</AllowedOrigin>gleiche Problem.)

In Cloudfront ist das Verteilungsverhalten der HTTP - Methoden ermöglichen gesetzt: GET, HEAD, OPTIONS. Die zwischengespeicherten Methoden sind dieselben. Forward Headers ist auf "Whitelist" gesetzt und diese Whitelist enthält "Access-Control-Request-Headers, Access-Control-Request-Method, Origin".

Die Tatsache, dass es nach einem Browser-Reload ohne Cache funktioniert, scheint darauf hinzudeuten, dass auf der S3 / CloudFront-Seite alles in Ordnung ist. Andernfalls wird der Inhalt geliefert. Aber warum wird der Inhalt dann nicht auf der ersten Seite angezeigt?

Ich arbeite in Google Chrome auf MacOS. Firefox hat kein Problem damit, die Dateien jedes Mal abzurufen. Opera bekommt NIE die Dateien. Safari nimmt die Bilder nach mehreren Aktualisierungen auf.

Mit curlbekomme ich keine Probleme:

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

Einige haben vorgeschlagen, die CloudFront-Distribution zu löschen und neu zu erstellen. Scheint eine ziemlich harte und unbequeme Lösung zu sein.

Was verursacht dieses Problem?

Aktualisieren:

Hinzufügen von Antwortheadern aus einem Bild, das nicht geladen werden konnte.

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
SunSparc
quelle
Sie haben Recht - löschen und neu erstellen ist extrem und sollte einfach nie notwendig sein. Können Sie uns die Anforderungs- und Antwortheader des Browsers für eine fehlgeschlagene Anforderung anzeigen? Und vielleicht für eine erfolgreiche Anfrage nach genau demselben Objekt?
Michael - sqlbot
@ Michael-sqlbot, ich hatte gehofft, Sie würden die URL ( smartystreets.com/contact ) besuchen und sehen, ob auf Ihrem Computer dasselbe passiert. :) Das Interessante an den Fehlern ist, dass der Browser, abgesehen von dem Fehler in der Konsole, den Status 200 meldet und das Image "(aus dem Festplatten-Cache)" verwendet, was mit Incognito I nicht möglich sein sollte habe gedacht. Auch nachdem ich den lokalen Cache geleert habe.
SunSparc
1
Ja, Leute "erfinden" so oft Domainnamen (die sich als echte Sites herausstellen, aber nicht die fragliche Site), dass ich anfangs nicht realisierte, dass Sie den tatsächlichen, korrekten Link zu Ihrer Site angegeben hatten. Vielen Dank dafür, Sie können meine Anfrage ignorieren. Ich kann das Problem duplizieren. Dies scheint ein clientseitiges Problem zu sein. Ich verfolge eine Theorie.
Michael - sqlbot
Ich denke, Sie könnten Recht haben, wenn es sich um ein clientseitiges Problem handelt. Die Bilder werden im HTML-Code mit A-Tags verknüpft, und es sieht dann so aus, als würden sie in der jQuery erneut angefordert. Möglicherweise kommt der Fehler von einem Anruf und der 200 von dem anderen.
SunSparc,
1
Genau das glaube ich. Chrome und S3 interagieren so, dass eine CORS-Anfrage, die einer Nicht-CORS-Anfrage für dasselbe Objekt folgt, unterbrochen wird. Wohl beide sind falsch ... aber wohl keiner von ihnen ist falsch. Ich denke nicht, dass Sie dies beheben können, ohne zwei Kopien des Objekts mit unterschiedlichen Schlüsseln zu speichern ... oder zwei unterschiedliche CloudFront-Distributionen (unterschiedliche Hostnamen) zu verwenden, sodass Sie nicht sowohl eine CORS- als auch eine Nicht-CORS-Anfrage stellen. Ich schreibe es mit Einzelheiten darüber, wie ich zu diesem Schluss komme, wenn Sie möchten.
Michael - sqlbot

Antworten:

55

Sie stellen zwei Anforderungen für dasselbe Objekt, eine aus HTML und eine aus XHR. Die zweite schlägt fehl, weil Chrome die zwischengespeicherte Antwort der ersten Anfrage verwendet, die keinen Access-Control-Allow-OriginAntwortheader hat.

Warum?

Chromium-Fehler 409090 Eine ursprungsübergreifende Anforderung aus dem Cache, die fehlschlägt, nachdem die reguläre Anforderung zwischengespeichert wurde, beschreibt dieses Problem und wird nicht behoben. Sie glauben, dass ihr Verhalten korrekt ist. Chrome betrachtet die zwischengespeicherte Antwort als verwendbar, anscheinend, weil die Antwort keinen Vary: OriginHeader enthielt .

S3 wird jedoch nicht zurückgegeben, Vary: Originwenn ein Objekt ohne Origin:Anforderungsheader angefordert wird , auch wenn CORS im Bucket konfiguriert ist. Vary: Originwird nur gesendet, wenn ein OriginHeader in der Anfrage vorhanden ist.

Und CloudFront fügt Vary: Originselbst dann keine OriginWhitelists für die Weiterleitung hinzu, was per Definition bedeuten sollte, dass eine Änderung des Headers die Antwort ändern kann - aus diesem Grund leiten Sie weiter und stellen den Cache für Anforderungsheader bereit.

CloudFront erhält einen Pass, da seine Antwort korrekt wäre, wenn S3 korrekter wäre, da CloudFront dies zurückgibt, wenn es von S3 bereitgestellt wird.

S3, etwas unschärfer. Es ist nicht falsch , zurückzukehren, Vary: Some-Headerwenn Some-Headerdie Anfrage keine enthält .

Zum Beispiel eine Antwort, die enthält

Vary: accept-encoding, accept-language

gibt an, dass der Ursprungsserver möglicherweise die Felder Accept-Encodingund die Accept-LanguageFelder der Anforderung (oder einen Mangel davon) als Bestimmungsfaktoren bei der Auswahl des Inhalts für diese Antwort verwendet hat. (Betonung hinzugefügt)

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

Es Vary: Some-Absent-Headerist eindeutig gültig, also wäre S3 richtig, wenn es Vary: Originzu seiner Antwort hinzugefügt würde , wenn CORS konfiguriert ist, da dies tatsächlich die Antwort variieren könnte.

Und dies würde Chrome anscheinend dazu bringen, das Richtige zu tun. Oder, wenn es in diesem Fall nicht das Richtige tut, würde es a verletzen MUST NOT. Aus demselben Abschnitt:

Ein Ursprungsserver sendet möglicherweise Varyeine Liste mit Feldern für zwei Zwecke:

  1. Um die Cache-Empfänger darüber zu informieren, dass sie MUST NOTdiese Antwort verwenden, um eine spätere Anforderung zu erfüllen, es sei denn, die spätere Anforderung enthält dieselben Werte für die aufgelisteten Felder wie die ursprüngliche Anforderung (Abschnitt 4.1 von [RFC7234]). Mit anderen Worten, Vary erweitert den Cache-Schlüssel, der erforderlich ist, um eine neue Anforderung mit dem gespeicherten Cache-Eintrag abzugleichen.

...

S3 SHOULDkehrt also wirklich zurück, Vary: Originwenn CORS im Bucket konfiguriert ist, wenn Origines in der Anforderung nicht vorhanden ist, aber nicht.

Trotzdem ist S3 nicht unbedingt falsch, wenn der Header nicht zurückgegeben wird, da es sich nur um a SHOULD, nicht um a handelt MUST. Aus demselben Abschnitt von RFC-7231:

Ein Ursprungsserver SHOULDsendet ein Vary-Header-Feld, wenn sein Algorithmus zum Auswählen einer Darstellung abhängig von anderen Aspekten der Anforderungsnachricht als der Methode und dem Anforderungsziel variiert.

Andererseits könnte argumentiert werden, dass Chrome implizit wissen sollte, dass das Variieren des OriginHeaders ein Cache-Schlüssel sein sollte, da dies die Antwort auf die gleiche Weise Authorizationändern könnte, wie dies die Antwort ändern könnte.

... es sei denn, die Varianz kann nicht überschritten werden oder der Ursprungsserver wurde absichtlich konfiguriert, um Cache-Transparenz zu verhindern. Es ist beispielsweise nicht erforderlich, den AuthorizationFeldnamen einzusenden, Varyda die Wiederverwendung zwischen Benutzern durch die Felddefinition [...] eingeschränkt wird.

In ähnlicher Weise wird die Wiederverwendung über die Ursprünge hinweg wohl durch die Natur der Sache eingeschränkt, Originaber dieses Argument ist kein starkes.


tl; dr: Sie können ein Objekt anscheinend nicht erfolgreich aus HTML abrufen und es dann als CORS-Anforderung mit Chrome und S3 (mit oder ohne CloudFront) erneut erfolgreich abrufen, da Besonderheiten in den Implementierungen vorliegen.


Problemumgehung:

Dieses Verhalten kann mit CloudFront und Lambda @ Edge umgangen werden, indem der folgende Code als Origin Response-Trigger verwendet wird.

Dies fügt Vary: Access-Control-Request-Headers, Access-Control-Request-Method, Originjeder Antwort von S3 hinzu, die keinen VaryHeader hat. Andernfalls wird der VaryHeader in der Antwort nicht geändert.

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

Namensnennung: Ich bin auch der Autor des ursprünglichen Posts in den AWS-Support-Foren, in denen dieser Code ursprünglich geteilt wurde.


Die oben beschriebene Lambda @ Edge-Lösung führt zu einem vollständig korrekten Verhalten. Je nach Ihren spezifischen Anforderungen können jedoch zwei Alternativen hilfreich sein:

Alternative / Problemumgehung Nr. 1: Schmieden Sie die CORS-Header in CloudFront.

CloudFront unterstützt benutzerdefinierte Header, die jeder Anfrage hinzugefügt werden. Wenn Sie Origin:für jede Anforderung festlegen , auch wenn es sich nicht um eine ursprungsübergreifende Anforderung handelt, wird das korrekte Verhalten in S3 aktiviert. Die Konfigurationsoption heißt "Custom Origin Headers", wobei das Wort "Origin" etwas völlig anderes bedeutet als in CORS. Wenn Sie einen benutzerdefinierten Header wie diesen in CloudFront konfigurieren, wird der in der Anforderung gesendete Wert mit dem angegebenen Wert überschrieben oder bei Abwesenheit hinzugefügt. Wenn Sie genau einen Ursprung haben, der über XHR auf Ihre Inhalte zugreift https://example.com, können Sie diesen hinzufügen. Die Verwendung *ist zweifelhaft, funktioniert jedoch möglicherweise in anderen Szenarien. Betrachten Sie die Auswirkungen sorgfältig.

Alternative / Problemumgehung Nr. 2: Verwenden Sie einen "Dummy" -Abfragezeichenfolgenparameter, der sich für HTML und XHR unterscheidet oder in der einen oder anderen nicht vorhanden ist. Diese Parameter werden normalerweise benannt x-*, sollten es aber nicht sein x-amz-*.

Nehmen wir an, Sie erfinden den Namen x-request. Also <img src="https://dzczcexample.cloudfront.net/image.png?x-request=html">. Fügen Sie den Abfrageparameter nicht hinzu, wenn Sie von JS aus auf das Objekt zugreifen. CloudFront macht bereits das Richtige, indem verschiedene Versionen der Objekte mithilfe des OriginHeaders oder ohne diesen als Teil des Cache-Schlüssels zwischengespeichert werden, da Sie diesen Header in Ihrem Cache-Verhalten weitergeleitet haben. Das Problem ist, dass Ihr Browser dies nicht weiß. Dies überzeugt den Browser, dass dies tatsächlich ein separates Objekt ist, das in einem CORS-Kontext erneut angefordert werden muss.

Wenn Sie diese alternativen Vorschläge verwenden, verwenden Sie den einen oder den anderen - nicht beide.

Michael - sqlbot
quelle
5
Ihre Antwort ist ein Lebensretter, tolle Antwort. Du hast mir ernsthafte Zeit gespart.
Mtyurt
Hallo, ich verwende keine Cloudfront für mein S3, daher hilft diese Problemumgehung nicht. Kann ich sonst noch etwas tun?
Jeffin
1
@ Jeffin, Alternative 2 oben funktioniert nur für S3 ohne CloudFront. Durch Hinzufügen eines beliebigen ?x-some-key=some-valueAbfragezeichenfolgenparameters wird der Browser davon überzeugt, dass die Anforderung anders ist.
Michael - sqlbot
1
@ Michael-Sqlbot: Ja, arbeitete wie ein Zauber
Jeffin
1
@ Lionel ja, das sieht richtig aus.
Michael - sqlbot
1

Ich weiß nicht, warum Sie von verschiedenen Browsern so unterschiedliche Ergebnisse erhalten, aber:

X-Amz-Cf-Id: wxn_m9meR6yPoyyvj1R7x83pBDPJy1nT7kdMv1aMwXVtHCunT9OC9g ==

In dieser Zeile wird ein CloudFront- oder Support-Techniker (sofern Sie darauf aufmerksam werden können) eine Ihrer fehlgeschlagenen Anfragen beantworten. Wenn die Anforderung an einen CloudFront-Server gesendet wird, sollte dieser Header in der Antwort enthalten sein. Wenn dieser Header nicht vorhanden ist, schlägt die Anforderung wahrscheinlich irgendwo fehl, bevor sie zu CloudFront gelangt.

unixguy
quelle
Vielen Dank, ich werde sehen, ob ich Antworten in den AWS-Foren erhalten kann.
SunSparc
1
Möglicherweise müssen Sie die 29 US-Dollar für die Entwicklerunterstützung bezahlen. Das ist eine triviale Summe für jedes Unternehmen, wenn man bedenkt, wie viel Zeit eine Person kostet.
Tim
1
@ Tim, beachte, dass die Entwicklerunterstützung nicht nur 29 US-Dollar beträgt. Das ist der Grundpreis. Wenn 3% Ihrer monatlichen AWS-Rechnung> = 29 USD sind, zahlen Sie 3% anstelle der Basis.
Michael - Sqlbot
Danke @ Michael-sqlbot, das habe ich nicht gemerkt. Ich weiß, dass sich der Supportpreis schnell summieren kann, wenn Sie beispielsweise reservierte Instanzen haben, aber ich habe mir noch nie die Entwicklerpreise angesehen, wenn Sie über viele Ressourcen verfügen.
Tim