Chrome S3 Cloudfront: Tidak ada tajuk 'Access-Control-Allow-Origin' pada permintaan XHR awal

30

Saya memiliki halaman web ( https://smartystreets.com/contact ) yang menggunakan jQuery untuk memuat beberapa file SVG dari S3 melalui CloudFront CDN.

Di Chrome saya akan membuka jendela Penyamaran serta konsol. Maka saya akan memuat halaman. Saat halaman dimuat, saya biasanya akan mendapatkan 6 hingga 8 pesan di konsol yang mirip dengan ini:

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.

Jika saya melakukan memuat ulang standar halaman, bahkan beberapa kali, saya terus mendapatkan kesalahan yang sama. Jika saya lakukan Command+Shift+Rmaka sebagian besar, dan kadang-kadang semua, gambar akan dimuat tanpa XMLHttpRequestkesalahan.

Kadang-kadang bahkan setelah gambar dimuat, saya akan menyegarkan dan satu atau lebih gambar tidak akan memuat dan mengembalikan XMLHttpRequestkesalahan itu lagi.

Saya telah memeriksa, mengubah, dan memeriksa ulang pengaturan pada S3 dan Cloudfront. Pada S3 konfigurasi CORS saya terlihat seperti ini:

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

(Catatan: awalnya hanya <AllowedOrigin>*</AllowedOrigin>memiliki masalah yang sama.)

Dalam CloudFront perilaku distribusi diatur untuk memungkinkan Metode HTTP: GET, HEAD, OPTIONS. Metode yang di-cache sama. Header Teruskan diatur ke "Daftar Putih" dan daftar putih itu termasuk, "Akses-Kontrol-Permintaan-Header, Akses-Kontrol-Permintaan-Metode, Asal".

Fakta bahwa ia berfungsi setelah browser memuat ulang tanpa cache tampaknya mengindikasikan bahwa semuanya baik-baik saja di sisi S3 / CloudFront, kalau tidak mengapa konten dikirimkan. Tapi mengapa konten tidak dikirim pada tampilan halaman awal?

Saya bekerja di Google Chrome di macOS. Firefox tidak memiliki masalah dalam mendapatkan file setiap saat. Opera TIDAK PERNAH mendapatkan file. Safari akan mengambil gambar setelah beberapa kali penyegaran.

Menggunakan curlSaya tidak mendapatkan masalah:

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

Beberapa menyarankan agar saya menghapus distribusi CloudFront dan membuatnya kembali. Sepertinya perbaikan yang agak keras dan tidak nyaman.

Apa yang menyebabkan masalah ini?

Memperbarui:

Menambahkan tajuk respons dari gambar yang gagal dimuat.

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
sumber
Anda benar - menghapus dan membuat ulang adalah hal yang ekstrem dan seharusnya tidak perlu dilakukan. Bisakah Anda menunjukkan kepada kami permintaan browser dan header tanggapan untuk permintaan yang gagal? Dan mungkin untuk permintaan yang berhasil dari objek yang sama persis?
Michael - sqlbot
@ Michael-sqlbot, saya agak berharap Anda akan mengunjungi URL ( smartystreets.com/contact ) dan melihat apakah hal yang sama terjadi pada mesin Anda. :) Yang menarik tentang kesalahan adalah bahwa selain dari kesalahan di konsol, browser melaporkan status 200, mengutip bahwa itu menggunakan gambar "(dari cache disk)", yang seharusnya tidak mungkin dengan Incognito, saya pikir. Bahkan setelah saya menghapus cache lokal.
SunSparc
1
Ya, orang-orang begitu sering "mengarang" nama domain (yang ternyata adalah situs nyata, tetapi bukan situs yang dimaksud) yang awalnya saya tidak sadari Anda telah memberikan tautan yang sebenarnya dan benar ke situs Anda. Terima kasih untuk itu, Anda dapat mengabaikan permintaan saya. Saya bisa menduplikasi masalahnya. Ini sepertinya masalah sisi klien. Saya mengejar teori.
Michael - sqlbot
Saya pikir Anda mungkin benar tentang itu menjadi masalah sisi klien. Gambar-gambar tersebut ditautkan dengan tag A dalam HTML dan kemudian sepertinya diminta lagi di jQuery. Mungkin kesalahannya adalah dari satu panggilan dan 200 berasal dari yang lain.
SunSparc
1
Itulah tepatnya yang saya yakini sebagai kasusnya. Chrome dan S3 berinteraksi dengan cara yang memecah permintaan CORS yang mengikuti permintaan non-CORS untuk objek yang sama. Bisa dibilang, keduanya salah ... tapi bisa dibilang, tak satu pun dari mereka yang salah. Saya tidak berpikir Anda dapat memperbaikinya tanpa menyimpan dua salinan objek dengan kunci yang berbeda ... atau menggunakan dua distribusi CloudFront yang berbeda (nama host berbeda) sehingga Anda tidak membuat permintaan CORS dan non-CORS. Saya akan menuliskannya dengan detail bagaimana saya sampai pada kesimpulan ini, jika Anda mau.
Michael - sqlbot

Jawaban:

57

Anda membuat dua permintaan untuk objek yang sama, satu dari HTML, satu dari XHR. Yang kedua gagal, karena Chrome menggunakan respons yang di-cache dari permintaan pertama, yang tidak memiliki Access-Control-Allow-Origintajuk respons.

Mengapa?

Bug Chromium 409090 Permintaan lintas asal dari cache gagal setelah permintaan reguler di-cache menjelaskan masalah ini, dan ini "tidak akan diperbaiki" - mereka yakin perilaku mereka benar. Chrome menganggap respons yang di-cache dapat digunakan, tampaknya karena respons tidak menyertakan Vary: Origintajuk.

Tetapi S3 tidak kembali Vary: Originketika suatu objek diminta tanpa Origin:header permintaan, bahkan ketika CORS dikonfigurasi pada bucket. Vary: Originhanya dikirim ketika ada Originheader dalam permintaan.

Dan CloudFront tidak menambahkan Vary: Originbahkan ketika masuk Origindaftar putih untuk penerusan, yang seharusnya secara definisi berarti bahwa memvariasikan tajuk dapat mengubah respons - itulah alasan mengapa Anda meneruskan dan cache terhadap tajuk permintaan.

CloudFront mendapat izin, karena responsnya akan benar jika S3 lebih benar, karena CloudFront mengembalikan ini ketika disediakan oleh S3.

S3, sedikit fuzzier. Tidak salah untuk kembali Vary: Some-Headerketika tidak ada Some-Headerpermintaan.

Misalnya, respons yang berisi

Vary: accept-encoding, accept-language

menunjukkan bahwa server asal mungkin telah menggunakan permintaan Accept-Encodingdan Accept-Languagebidang (atau ketiadaan) sebagai faktor penentu saat memilih konten untuk respons ini. (penekanan ditambahkan)

https://tools.ietf.org/html/rfc7231#s-7-7.4.4

Jelas, Vary: Some-Absent-Headervalid, jadi S3 akan benar jika ditambahkan Vary: Originke responsnya jika CORS dikonfigurasi, karena memang bisa beragam respons.

Dan, tampaknya, ini akan membuat Chrome melakukan hal yang benar. Atau, jika tidak melakukan hal yang benar dalam kasus ini, itu akan melanggar a MUST NOT. Dari bagian yang sama:

Server asal mungkin mengirim Varydengan daftar bidang untuk dua tujuan:

  1. Untuk memberi tahu penerima cache bahwa mereka MUST NOTmenggunakan respons ini untuk memenuhi permintaan di kemudian hari kecuali permintaan di kemudian hari memiliki nilai yang sama untuk bidang yang terdaftar dengan permintaan asli (Bagian 4.1 dari [RFC7234]). Dengan kata lain, Vary memperluas kunci cache yang diperlukan untuk mencocokkan permintaan baru dengan entri cache yang disimpan.

...

Jadi, S3 benar SHOULD- benar akan kembali Vary: Originketika CORS dikonfigurasi pada bucket, jika Origintidak ada dari permintaan, tetapi tidak.

Namun, S3 tidak sepenuhnya salah untuk tidak mengembalikan header, karena itu hanya a SHOULD, bukan a MUST. Sekali lagi, dari bagian yang sama dari RFC-7231:

Server asal SHOULDmengirim bidang header Vary ketika algoritme untuk memilih representasi bervariasi berdasarkan aspek pesan permintaan selain metode dan target permintaan, ...

Di sisi lain, argumen dapat dibuat bahwa Chrome harus secara implisit mengetahui bahwa memvariasikan Originheader harus menjadi kunci cache karena itu dapat mengubah respons dengan cara yang sama Authorizationdapat mengubah respons.

... kecuali varians tidak dapat dilintasi atau server asal telah sengaja dikonfigurasikan untuk mencegah transparansi cache. Misalnya, tidak perlu mengirim Authorizationnama bidang Varykarena penggunaan ulang di pengguna dibatasi oleh definisi bidang [...]

Demikian pula, penggunaan kembali lintas asal dapat diperdebatkan oleh sifat Origintetapi argumen ini tidak kuat.


tl; dr: Anda tampaknya tidak berhasil mengambil objek dari HTML dan kemudian berhasil mengambilnya kembali dengan permintaan CORS dengan Chrome dan S3 (dengan atau tanpa CloudFront), karena kekhasan dalam implementasi.


Penanganan masalah:

Perilaku ini dapat diatasi dengan CloudFront dan Lambda @ Edge, menggunakan kode berikut sebagai pemicu Respons Asal.

Ini menambah Vary: Access-Control-Request-Headers, Access-Control-Request-Method, Originrespons apa pun dari S3 yang tidak memiliki Varytajuk. Jika tidak, Varytajuk dalam respons tidak diubah.

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

Atribusi: Saya juga penulis posting asli di forum Dukungan AWS tempat kode ini awalnya dibagikan.


Solusi Lambda @ Edge di atas menghasilkan perilaku yang sepenuhnya benar, tetapi berikut adalah dua alternatif yang mungkin berguna bagi Anda, tergantung pada kebutuhan spesifik Anda:

Alternatif / Hackaround # 1: Tempa header CORS di CloudFront.

CloudFront mendukung header kustom yang ditambahkan ke setiap permintaan. Jika Anda menetapkan Origin:pada setiap permintaan, bahkan yang tidak berasal dari silang, ini akan memungkinkan perilaku yang benar dalam S3. Opsi konfigurasi disebut Custom Origin Header, dengan kata "Origin" yang berarti sesuatu yang sama sekali berbeda dari artinya di CORS. Mengkonfigurasi tajuk khusus seperti ini di CloudFront menimpa apa yang dikirim dalam permintaan dengan nilai yang ditentukan, atau menambahkannya jika tidak ada. Jika Anda memiliki satu asal tepat mengakses konten Anda melalui XHR, misalnya https://example.com, Anda dapat menambahkannya. Penggunaan *meragukan, tetapi mungkin berhasil untuk skenario lain. Pertimbangkan implikasinya dengan cermat.

Alternatif / Peretasan # 2: Gunakan parameter string kueri "dummy" yang berbeda untuk HTML dan XHR atau tidak ada satu atau yang lain. Parameter ini biasanya dinamai x-*tetapi tidak boleh x-amz-*.

Katakanlah Anda membuat nama x-request. Jadi <img src="https://dzczcexample.cloudfront.net/image.png?x-request=html">. Saat mengakses objek dari JS, jangan tambahkan parameter kueri. CloudFront sudah melakukan hal yang benar, dengan melakukan caching berbagai versi objek menggunakan Originheader atau tidak adanya itu sebagai bagian dari kunci cache, karena Anda meneruskan header itu dalam perilaku cache Anda. Masalahnya, browser Anda tidak mengetahui hal ini. Ini meyakinkan browser bahwa ini sebenarnya adalah objek terpisah yang perlu diminta lagi, dalam konteks CORS.

Jika Anda menggunakan saran alternatif ini, gunakan satu atau yang lain - tidak keduanya.

Michael - sqlbot
sumber
5
Respons Anda adalah penyelamat, jawaban yang bagus. Anda menyelamatkan saya waktu yang serius.
mtyurt
Hai, saya tidak menggunakan cloudfront untuk s3 saya jadi solusi ini tidak membantu, apakah ada hal lain yang bisa saya lakukan?
Jeffin
1
@ Jeffin, alternatif # 2 di atas akan bekerja untuk S3 saja, tanpa CloudFront. Menambahkan ?x-some-key=some-valueparameter string kueri arbitrer akan meyakinkan browser bahwa permintaannya berbeda.
Michael - sqlbot
1
@ Michael-sqlbot: Yap, bekerja seperti pesona
Jeffin
1
@ Lionel ya, itu terlihat benar.
Michael - sqlbot
1

Saya tidak tahu mengapa Anda akan mendapatkan hasil yang berbeda dari berbagai browser, tetapi:

X-Amz-Cf-Id: wxn_m9meR6yPoyyvj1R7x83pBDPJy1nT7kdMv1aMwXVtHCunT9OC9g ==

Baris itu ada apa (jika Anda bisa mendapatkan perhatian mereka) seorang insinyur CloudFront atau Dukungan akan digunakan untuk mengikuti salah satu permintaan Anda yang gagal. Jika permintaan menuju ke server CloudFront, ia harus memiliki header ini sebagai respons. Jika tajuk itu tidak ada, maka permintaan kemungkinan gagal di suatu tempat sebelum sampai ke CloudFront.

unixguy
sumber
Terima kasih, saya akan melihat apakah saya bisa mendapatkan tanggapan di forum AWS.
SunSparc
1
Anda mungkin perlu membayar $ 29 untuk dukungan pengembang. Itu adalah jumlah uang sepele untuk bisnis apa pun, mengingat berapa biaya waktu seseorang.
Tim
1
@Tim, perhatikan bahwa dukungan pengembang bukan hanya $ 29. Itu harga dasar. Jika 3% dari tagihan AWS bulanan Anda> = $ 29, Anda membayar 3% sebagai ganti basis.
Michael - sqlbot
Terima kasih @ Michael-sqlbot, saya tidak menyadarinya. Saya tahu harga dukungan dapat bertambah dengan cepat ketika Anda memiliki hal-hal seperti contoh yang dipesan, tetapi saya tidak pernah melihat harga pengembang ketika Anda memiliki banyak sumber daya.
Tim