Frage Chrome S3 Cloudfront: Keine 'Access-Control-Allow-Origin'-Kopfzeile bei der ersten XHR-Anforderung


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 werde ich die Seite laden. Wenn die Seite geladen wird, erhalte ich in der Konsole normalerweise 6 bis 8 Nachrichten, die ähnlich 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 neu lade, sogar mehrere Male, bekomme ich immer die gleichen Fehler. Wenn ich mache Command+Shift+R dann werden die meisten und manchmal alle Bilder ohne die geladen XMLHttpRequest Error.

Manchmal, auch nachdem die Bilder geladen wurden, werde ich aktualisieren und eines oder mehrere der Bilder werden nicht geladen und das zurückgeben XMLHttpRequest Fehler erneut.

Ich habe die Einstellungen in S3 und Cloudfront überprüft, geändert und erneut überprüft. In S3 sieht meine CORS-Konfiguration folgendermaßen 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>

(Anmerkung: hatte ursprünglich nur <AllowedOrigin>*</AllowedOrigin>, gleiches Problem.)

In CloudFront ist das Verteilungsverhalten so eingestellt, dass die HTTP-Methoden erlaubt sind: GET, HEAD, OPTIONS. Cache-Methoden sind gleich. Forward Headers ist auf "Whitelist" gesetzt und diese Whitelist enthält "Access-Control-Request-Header, Access-Control-Request-Methode, Origin".

Die Tatsache, dass es nach einem Cache-less-Browser-Reload funktioniert, scheint darauf hinzudeuten, dass alles gut auf der S3 / CloudFront-Seite ist, sonst würde der Inhalt geliefert werden. Aber warum wurde der Inhalt nicht auf der ersten Seite angezeigt?

Ich arbeite in Google Chrome auf macOS. Firefox hat kein Problem, die Dateien jedes Mal zu bekommen. Opera erhält NIE die Dateien. Safari nimmt die Bilder nach mehreren Aktualisierungen auf.

Verwenden curl Ich habe 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, dass ich die CloudFront-Distribution lösche und neu erstelle. Scheint wie eine ziemlich harte und unbequeme Lösung.

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

20
2018-06-20 18:54


Ursprung


Sie haben Recht - Löschen und Wiederherstellen ist extrem und sollte einfach nie notwendig sein. Können Sie uns die Anfrage- und Antwortheader des Browsers für eine fehlgeschlagene Anfrage zeigen? Und vielleicht für eine erfolgreiche Anfrage genau das gleiche Objekt? - Michael - sqlbot
@ Michael-sqlbot, ich hatte gehofft, du würdest die URL besuchen (smartystreets.com/kontakt) und sehen Sie, ob das Gleiche auf Ihrer Maschine passiert. :) Das Interessante an den Fehlern ist, dass der Browser neben dem Fehler in der Konsole einen Status von 200 meldet, mit der Begründung, dass er das Image "from cache disk" verwendet, was mit Inkognito nicht möglich sein sollte, I habe gedacht. Auch nachdem ich den lokalen Cache gelöscht habe. - SunSparc
Ja, Leute "schminken" oft Domainnamen (die sich als echte Seiten herausstellen, aber nicht als fragliche Seite), von denen ich anfangs nicht wusste, dass Sie den richtigen Link zu Ihrer Seite angegeben haben. Danke dafür, Sie können meine Anfrage ignorieren. Ich kann das Problem duplizieren. Dies scheint ein Problem auf der Clientseite zu sein. Ich jage eine Theorie nach. - Michael - sqlbot
Ich denke, Sie haben vielleicht recht, dass es sich um ein clientseitiges Problem handelt. Die Bilder werden mit A-Tags im HTML verknüpft und dann sieht es so aus, als ob sie in der jQuery erneut angefordert werden. Vielleicht ist der Fehler von einem Anruf und der 200 ist von dem anderen. - SunSparc
Genau das ist meiner Meinung nach der Fall. Chrome und S3 interagieren auf eine Weise, die eine CORS-Anforderung unterbricht, die auf eine Nicht-CORS-Anforderung für dasselbe Objekt folgt. Beide liegen falsch, aber wohl ist keiner der beiden falsch. Ich glaube nicht, dass Sie das beheben können, ohne zwei Kopien des Objekts mit unterschiedlichen Schlüsseln zu speichern ... oder zwei verschiedene CloudFront-Distributionen (verschiedene Hostnamen) zu verwenden, so dass Sie nicht sowohl eine CORS- als auch eine Nicht-CORS-Anfrage machen. Ich werde es mit Einzelheiten darüber, wie ich zu dieser Schlussfolgerung komme, aufschreiben, wenn Sie möchten. - Michael - sqlbot


Antworten:


Sie machen zwei Anfragen für dasselbe Objekt, eines aus HTML, eines aus XHR. Der zweite schlägt fehl, weil Chrome die zwischengespeicherte Antwort von der ersten Anfrage verwendet, die keine hat Access-Control-Allow-Origin Antwortheader

Warum?

Chromium Bug 409090 Cross-Ursprungs-Anfrage vom Cache fehlgeschlagen, nachdem reguläre Anfrage zwischengespeichert wurde beschreibt dieses Problem, und es ist ein "nicht beheben" - sie glauben, dass ihr Verhalten korrekt ist. Chrome betrachtet die zwischengespeicherte Antwort als brauchbar, offenbar weil die Antwort kein a enthielt Vary: Origin Header.

Aber S3 kehrt nicht zurück Vary: Origin wenn ein Objekt ohne ein angefordert wird Origin: Anforderungsheader, selbst wenn CORS für den Bucket konfiguriert ist. Vary: Origin wird nur gesendet, wenn ein Origin Header ist in der Anfrage vorhanden.

Und CloudFront fügt nicht hinzu Vary: Origin sogar wenn Origin Wird für die Weiterleitung auf die weiße Liste gesetzt, was definitionsgemäß bedeuten sollte, dass die Änderung der Kopfzeile die Antwort ändern könnte - das ist der Grund, warum Sie die Anfrage-Kopfzeilen weiterleiten und zwischenspeichern.

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

S3, ein wenig fuzzer. Es ist nicht falsch zurückgeben Vary: Some-Header als es keine gab Some-Header in der Anfrage.

Zum Beispiel eine Antwort, die enthält

Vary: accept-encoding, accept-language

gibt an, dass der Ursprungsserver die Anfrage möglicherweise verwendet hat    Accept-Encoding und Accept-Language Felder (oder Fehlen davon) wie   Bestimmung von Faktoren bei der Auswahl des Inhalts für diese Antwort. (Betonung hinzugefügt)

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

Deutlich, Vary: Some-Absent-Header ist gültig, also wäre S3 korrekt, wenn es hinzugefügt würde Vary: Origin zu seiner Antwort, wenn CORS konfiguriert ist, da dies in der Tat die Antwort variieren könnte.

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

Ein Ursprungsserver könnte senden Vary mit einer Liste von Feldern für zwei      Zwecke:

  1. Um Cache-Empfänger zu informieren, dass sie MUST NOT Verwenden Sie diese Antwort      um eine spätere Anfrage zu erfüllen, es sei denn, die spätere Anfrage hat dieselbe      Werte für die aufgelisteten Felder als ursprüngliche Anfrage (Abschnitt 4.1      von [RFC7234]). Mit anderen Worten, Vary erweitert den Cache-Schlüssel      erforderlich, um eine neue Anfrage an den gespeicherten Cache-Eintrag anzupassen.

...

Also, S3 wirklich SHOULD zurückkommen Vary: Origin wenn CORS für den Bucket konfiguriert ist, wenn Origin fehlt in der Anfrage, tut es aber nicht.

Dennoch ist S3 nicht unbedingt falsch, wenn der Header nicht zurückgegeben wird, weil es nur ein SHOULD, kein MUST. Noch einmal, aus dem gleichen Abschnitt von RFC-7231:

Ein Ursprungsserver SHOULD Sendet ein Vary-Header-Feld wenn sein Algorithmus      zum Auswählen einer Repräsentation variiert basierend auf Aspekten der Anfrage      Nachricht außer der Methode und Anfrage Ziel, ...

Auf der anderen Seite könnte man argumentieren, dass Chrome implizit wissen sollte, dass die Origin Der Header sollte ein Cache-Schlüssel sein, da er die Antwort auf die gleiche Weise ändern kann Authorization könnte die Antwort ändern.

... außer die Varianz      kann nicht gekreuzt werden oder der Ursprungsserver wurde absichtlich      konfiguriert, um Cache-Transparenz zu verhindern. Zum Beispiel gibt es keine      müssen senden die Authorization Feldname in Vary weil Wiederverwendung      across Benutzer ist durch die Felddefinition [...] eingeschränkt

In ähnlicher Weise ist die Wiederverwendung über die Ursprünge hinweg wohl durch die Art der Origin aber dieses Argument ist nicht stark.


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


Problemumgehung:

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

Dies fügt hinzu Vary: Access-Control-Request-Headers, Access-Control-Request-Method, Origin auf jede Antwort von S3, die keine hat Vary Header. Ansonsten der Vary Header in der Antwort wird 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 von der ursprüngliche Beitrag in den AWS Support-Foren, in denen dieser Code ursprünglich geteilt wurde.


Die obige Lambda @ Edge-Lösung führt zu vollständig korrektem Verhalten, aber hier sind zwei Alternativen, die Sie je nach Ihren spezifischen Anforderungen nützlich finden können:

Alternative / Hackaround # 1: Forge die CORS-Header in CloudFront.

CloudFront unterstützt benutzerdefinierte Header, die jeder Anforderung hinzugefügt werden. Wenn Sie einstellen Origin: Bei jeder Anfrage, auch bei denen, die nicht vom Ursprung her stammen, wird dies das richtige Verhalten in S3 ermöglichen. Die Konfigurationsoption heißt Custom Origin Header, wobei das Wort "Origin" etwas völlig anderes bedeutet als in CORS. Durch die Konfiguration eines benutzerdefinierten Headers in CloudFront wird überschrieben, was in der Anforderung mit dem angegebenen Wert gesendet wurde, oder bei fehlender Angabe hinzugefügt. Wenn Sie haben genau ein Ursprung, der auf Ihren Inhalt über XHR zugreift, z. https://example.com, können Sie das hinzufügen. Verwenden * ist zweifelhaft, könnte aber für andere Szenarien funktionieren. Betrachten Sie die Implikationen sorgfältig.

Alternative / Hackaround # 2: Verwenden Sie einen "Dummy" -Abfragezeichenfolgenparameter, der für HTML und XHR unterschiedlich ist oder in der einen oder anderen nicht vorhanden ist. Diese Parameter werden normalerweise benannt x-* aber sollte nicht sein x-amz-*.

Nehmen wir an, Sie bilden den Namen x-request. So <img src="https://dzczcexample.cloudfront.net/image.png?x-request=html">. Wenn Sie von JS aus auf das Objekt zugreifen, fügen Sie den Abfrageparameter nicht hinzu. CloudFront macht bereits das Richtige, indem es verschiedene Versionen der Objekte mit dem Caching zwischenspeichert Origin Header oder Abwesenheit davon als Teil des Cache-Schlüssels, weil Sie diesen Header in Ihrem Cache-Verhalten weitergeleitet haben. Das Problem ist, dass Ihr Browser das 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 das eine oder das andere - nicht beides.


33
2018-06-21 00:31



Tolle Information, danke! - SunSparc
Ihre Antwort ist ein Lebensretter, große Antwort. Du hast mir etwas Zeit gespart. - mtyurt
Hallo, ich benutze keine Cloudfront für meine S3, also hilft diese Abhilfe nicht, gibt es noch etwas, das ich tun kann? - Jeffin
@ Michael-sqlbot: Ja, arbeitete wie ein Charme - Jeffin
@Lionel ja, das sieht richtig aus. - Michael - sqlbot


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

X-Amz-Cf-ID: wxn_m9meR6yPoyyvj1R7x83pBDPJy1nT7kdMv1aMwXVtHCunT9OC9g ==

Diese Zeile ist genau das Richtige (wenn Sie ihre Aufmerksamkeit erhalten), wird ein CloudFront- oder Support-Techniker einer Ihrer fehlgeschlagenen Anfragen folgen. Wenn die Anforderung einen CloudFront-Server erreicht, sollte dieser Header in der Antwort enthalten sein. Wenn dieser Header nicht vorhanden ist, schlägt die Anforderung wahrscheinlich irgendwo fehl, bevor sie an CloudFront gelangt.


1
2018-06-20 21:05



Danke, ich werde sehen, ob ich irgendwelche Antworten in den AWS-Foren bekommen kann. - SunSparc
Möglicherweise müssen Sie die 29 USD für den Entwickler-Support bezahlen. Das ist eine triviale Menge an Geld für jedes Geschäft, wenn man bedenkt, wie viel eine Person Zeit kostet. - Tim
@Tim, beachten Sie, dass Entwickler-Support nicht nur $ 29 ist. Das ist der Grundpreis. Wenn 3% Ihrer monatlichen AWS-Rechnung> = 29 $ beträgt, zahlen Sie 3% anstelle der Basis. - Michael - sqlbot
Danke @ Michael-sqlbot, das habe ich nicht bemerkt. Ich weiß, dass der Support-Preis sich schnell summieren kann, wenn Sie Dinge wie reservierte Instanzen haben, aber ich habe nie die Entwicklerpreise betrachtet, wenn Sie viele Ressourcen haben. - Tim