Chrome S3 Cloudfront: İlk XHR isteğinde 'Erişim Kontrolü-İzin Verme-Kaynağı Yok' başlığı yok


30

Bazı SVG dosyalarını S3'ten CloudFront CDN'ye yüklemek için jQuery kullanan bir web sayfam var ( https://smartystreets.com/contact ).

Chrome'da, konsolun yanı sıra bir Gizli penceresi de açacağım. Sonra sayfayı yükleyeceğim. Sayfa yüklenirken, tipik olarak konsolda buna benzeyen 6 ila 8 ileti alıyorum:

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.

Sayfanın standart bir yeniden yüklemesini yaparsam, çok zaman bile olsa aynı hataları almaya devam ediyorum. Bunu yaparsam Command+Shift+R, görüntülerin çoğunu bazen de hepsini XMLHttpRequesthatasız yükleyeceğim .

Bazen görüntüler yüklendikten sonra bile, yenilenir ve görüntülerden bir veya daha fazlası yüklenmez ve bu XMLHttpRequesthatayı tekrar döndürmez .

S3 ve Cloudfront'taki ayarları kontrol ettim, değiştirdim ve yeniden kontrol ettim. S3'te CORS konfigürasyonum şöyle gözüküyor:

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

(Not: başlangıçta yalnızca <AllowedOrigin>*</AllowedOrigin>aynı problem vardı .)

CloudFront yılında dağıtım davranışı HTTP Yöntemleri izin verecek şekilde ayarlanır: GET, HEAD, OPTIONS. Önbelleğe alınmış yöntemler aynıdır. İleri Başlıklar "Beyaz Liste" olarak ayarlanmıştır ve bu beyaz liste "Erişim Denetimi-İsteği-Başlıkları, Erişim Denetimi-İsteği-Yöntemi, Kökeni" içerir.

Önbelleksiz bir tarayıcı yeniden yüklendikten sonra çalışması, S3 / CloudFront tarafında her şeyin yolunda olduğunu, içeriğin neden yayınlandığını gösteriyor gibi görünüyor. Peki o zaman içerik neden ilk sayfa görünümünde yayınlanmıyor?

MacOS'ta Google Chrome'da çalışıyorum. Firefox'un dosyaları her zaman almakta bir sorunu yok. Opera ASLA dosyaları almaz. Safari birkaç yenilemeden sonra görüntüleri alacak.

Kullanarak curlherhangi bir sorun alamıyorum:

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

Bazıları CloudFront dağıtımını silmemi ve yeniden oluşturmamı önerdi. Oldukça sert ve uygunsuz bir düzeltme gibi görünüyor.

Bu soruna ne sebep oluyor?

Güncelleştirme:

Yüklenemeyen bir görüntüden yanıt başlıkları ekleme.

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

Haklısınız - silme ve yeniden yaratma aşırı derecede önemlidir ve hiçbir zaman gerekli olmamalıdır. Başarısız bir istek için tarayıcının istek ve yanıt başlıklarını bize gösterebilir misiniz? Ve belki de tam olarak aynı nesnenin başarılı bir talebi için?
Michael - sqlbot

@ Michael-sqlbot, URL'yi ( smartystreets.com/contact ) ziyaret edip makinenizde aynı şeyin olup olmadığını görmeyi umuyordum . :) Hatalarla ilgili ilginç olan şey, konsoldaki hatanın yanı sıra, tarayıcının "durumunun" (disk önbellekten gelen) "görüntüsünü kullandığını ve Incognito ile mümkün olmaması gerektiğini belirten 200 durumunu bildirmesidir. düşündük. Yerel önbelleği temizledikten sonra bile.
SunSparc

1
Evet, insanlar o kadar sık ​​sık etki alanı adları (gerçek siteler olduğu ortaya çıkıyor, ancak söz konusu site değil) başlangıçta sitenize gerçek, doğru bağlantıyı verdiğinizi fark etmediler. Bunun için teşekkürler, isteğimi göz ardı edebilirsiniz. Sorunu çoğaltabilirim. Bu müşteri tarafı sorunu gibi görünüyor. Bir teoriyi takip ediyorum.
Michael - sqlbot

Bir müşteri tarafı sorunu olduğu konusunda haklı olabileceğinizi düşünüyorum. Görüntüler, HTML'deki A etiketleriyle ilişkilendirilir ve daha sonra jQuery'de tekrar istenir gibi görünür. Belki de hata bir çağrıdan ve 200 diğerinden.
SunSparc

1
Bu tam olarak böyle olduğuna inandığım şey. Chrome ve S3, aynı nesne için CORS dışı bir isteği izleyen bir CORS isteğini kesecek şekilde etkileşimde bulunuyorlar. Tartışmalı olarak, ikisi de yanlış ... ama tartışmalı, ikisi de yanlış değil. Bunu, nesnenin iki kopyasını farklı anahtarlarla saklamadan ya da iki farklı CloudFront dağıtımı (farklı ana bilgisayar adları) kullanmadan düzeltemeyeceğinizi düşünüyorum, böylece hem CORS hem de CORS dışı bir istek yapmazsınız. İsterseniz bu sonuca nasıl varacağımı ayrıntılarıyla birlikte yazacağım.
Michael - sqlbot

Yanıtlar:


55

Aynı nesne için, biri HTML, biri XHR olmak üzere iki istek yapıyorsunuz. İkincisi başarısız olur, çünkü Chrome, ilk istekden gelen önbelleğe alınmış yanıtı kullanır; Access-Control-Allow-Originyanıt başlığı yoktur .

Niye ya?

Chromium bug 409090 Düzenli istek önbelleğe alındıktan sonra önbellekten gelen çapraz kaynaklı istek, bu sorunu açıklıyor ve bu bir "düzeltmeyecek" - davranışlarının doğru olduğuna inanıyorlar. Chrome, önbelleğe alınmış yanıtın kullanılabilir olduğunu düşünmektedir, çünkü görünüşe göre yanıt bir Vary: Originbaşlık içermemektedir .

Ancak S3 , kovada yapılandırılmış olsa bile, istek başlığı Vary: Originolmadan bir nesne talep edildiğinde S3 geri dönmüyor Origin:. Vary: Originsadece Origintalepte bir başlık bulunduğunda gönderilir .

CloudFront, yönlendirme için beyaz listeye Vary: Originalındığında bile eklemez Origin; bu, tanımı gereği, başlığın değiştirilmesinin yanıtı değiştirebileceği anlamına gelir;

CloudFront bir pas aldı, çünkü eğer S3'ler daha doğru olsaydı, cevabı doğru olurdu, çünkü CloudFront bunu S3 tarafından sağlandığında geri verdi.

S3, biraz karışık. Öyle yanlış değil dönmek Vary: Some-Headerhiçbir varken Some-Headeristekte.

Örneğin, içeren bir cevap

Vary: accept-encoding, accept-language

kaynak sunucu , bu yanıt için içeriği seçerken isteğin Accept-Encodingve Accept-Languagealanların (veya eksikliğinin) belirleyici faktörler olarak kullanmış olabileceğini gösterir . (vurgu eklenmiştir)

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

Açıkça Vary: Some-Absent-Headergeçerlidir, bu nedenle Vary: OriginCORS yapılandırılmışsa cevabına eklenirse S3 doğru olacaktır , çünkü bu aslında cevabı değiştirebilir.

Ve görünüşe göre, bu Chrome'un doğru olanı yapmasını sağlar. Veya bu durumda doğru olanı yapmazsa, a'yı ihlal eder MUST NOT. Aynı bölümden:

Bir kaynak sunucu Vary, iki amaçla bir alan listesiyle gönderebilir :

  1. Önbellek alıcılarına MUST NOTbu cevabı, sonraki istek orijinal istekler ile listelenen alanlar için aynı değerlere sahip olmadıkça, daha sonraki bir talebi yerine getirmek için kullandıklarını bildirmek için ([RFC7234] Bölüm 4.1). Başka bir deyişle, Vary, yeni bir isteği depolanan önbellek girdisiyle eşleştirmek için gereken önbellek anahtarını genişletir.

...

Yani, S3 gerçekten CORS kovada yapılandırıldığında, eğer talepte bulunmuyorsa SHOULDgeri dönüyor , ancak yapmıyor.Vary: OriginOrigin

Yine de, S3 başlığın döndürülmediği için kesinlikle yanlıştır, çünkü bu sadece bir a SHOULDdeğil MUST. Yine, RFC-7231'in aynı bölümünden:

Bir kaynak sunucu SHOULD, bir temsil seçme algoritması, yöntem ve istek hedefi dışındaki istek mesajının özelliklerine göre değiştiğinde, Vary başlık alanı gönderir.

Öte yandan, Chrome'un, Originbaşlığın değiştirilmesinin bir önbellek anahtarı olması gerektiğini kesin olarak bilmesi gerektiği, çünkü cevabı Authorization, cevabı değiştirebileceği şekilde değiştirebileceği iddiası yapılabilir .

... varyans aşılamazsa veya kaynak sunucu önbellek şeffaflığını önlemek için kasten yapılandırılmadıysa. Örneğin, Authorizationalan adını göndermeye gerek yoktur Varyçünkü kullanıcılar arasında yeniden kullanım alan tanımı ile sınırlıdır [...]

Benzer şekilde, kökenleri yeniden kullanmak tartışmalı bir şekilde doğası gereği sınırlandırılmıştır, Originancak bu argüman güçlü değildir.


tl; dr: Görünüşe göre başarılı bir şekilde HTML'den bir nesneyi alamıyor ve ardından uygulamalardaki özellikleri nedeniyle Chrome ve S3 (CloudFront ile veya olmadan) CORS isteği ile başarıyla getiremiyorsunuz.


Çözüm:

Bu davranış, bir Origin Response tetikleyicisi olarak aşağıdaki kodu kullanarak CloudFront ve Lambda @ Edge ile çözülebilir.

Bu Vary: Access-Control-Request-Headers, Access-Control-Request-Method, Origin, S3'ten Varybaşlığı olmayan herhangi bir yanıtı ekler . Aksi takdirde, Varyyanıttaki başlık değiştirilmez.

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

Atıf: Ayrıca, bu kodun başlangıçta paylaşıldığı AWS Destek forumlarındaki orijinal yazının da yazarıyım.


Yukarıdaki Lambda @ Edge çözümü tam olarak doğru davranışla sonuçlanıyor, ancak özel gereksinimlerinize bağlı olarak yararlı bulabileceğiniz iki alternatif:

Alternatif / Hackaround # 1: CloudFront'daki CORS başlıklarını oluşturun.

CloudFront, her talebe eklenen özel başlıkları destekler. Eğer ayarlarsanız Origin:bile, her istekte çapraz kökenli olmayanlar da, bu S3 doğru davranış sağlayacaktır. Konfigürasyon seçeneğine Özel Menşe Başlıkları denir ve "Origin" kelimesi CORS'takinden tamamen farklı bir şey anlamına gelir. CloudFront'ta böyle bir özel başlığın yapılandırılması, istekte gönderilen değerin belirtilen değerle üzerine yazıldığını belirtir veya yoksa ekler. Eğer varsa tam olarak örneğin XHR'nin, üzerine içeriğe erişmesini bir kökeni https://example.com, bunu ekleyebilir. Kullanımı *şüphelidir, ancak diğer senaryolarda işe yarayabilir. Etkilerini dikkatlice düşünün.

Alternative / Hackaround # 2: HTML ve XHR için farklı olan veya birinden diğerinde olmayan bir "boş" sorgu dizesi parametresi kullanın. Bu parametreler tipik olarak adlandırılır x-*ancak olmamalıdır x-amz-*.

Diyelim ki adı uydurdun x-request. Yani <img src="https://dzczcexample.cloudfront.net/image.png?x-request=html">. JS'den nesneye erişirken, sorgu parametresini eklemeyin. CloudFront zaten bir şeyi Originönbellek anahtarının bir parçası olarak kullanarak veya önbellek anahtarının bir parçası olarak kullanarak nesnelerin farklı sürümlerini önbelleğe alarak zaten doğru olanı yapıyor . Sorun şu ki, tarayıcınız bunu bilmiyor. Bu tarayıcıyı, bunun CORS bağlamında tekrar talep edilmesi gereken ayrı bir nesne olduğuna ikna eder.

Bu alternatif önerileri kullanıyorsanız, ikisinden birini değil birini kullanın.


5
Yanıtınız bir cankurtaran, büyük cevap. Bana ciddi zaman kazandırdın.
mtyurt

Merhaba, s3'üm için cloudfront kullanmıyorum, bu nedenle bu geçici çözüm yardımcı olmuyor, yapabileceğim başka bir şey var mı?
Jeffin

1
@Jeffin, yukarıdaki 2 numaralı alternatif, CloudFront olmadan yalnızca S3 için çalışacaktır. İsteğe bağlı bir ?x-some-key=some-valuesorgu dizesi parametresi eklemek , tarayıcıyı isteğin farklı olduğuna ikna edecektir.
Michael - sqlbot

1
@ Michael-sqlbot: Evet, bir cazibe gibi çalıştı
Jeffin

1
@Lionel evet, bu doğru görünüyor.
Michael - sqlbot

1

Çeşitli tarayıcılardan neden bu kadar farklı sonuçlar elde edersiniz bilmiyorum ama:

X-Amz-Cf-Id: wxn_m9meR6yPoyyvj1R7x83pBDPJy1nT7kdMv1aMwXVtHCunT9OC9g ==

Buradaki çizgi, dikkatlerini toplayabilmeniz durumunda, bir CloudFront veya Destek mühendisinin başarısız taleplerinizden birini takip etmek için kullanacağı şeydir. İstek bir CloudFront sunucusuna ulaşıyorsa, yanıtta bu başlığa sahip olmalıdır. Bu başlık orada değilse, istek CloudFront'a ulaşmadan önce bir yerde başarısız olabilir.


Teşekkürler, AWS forumlarında herhangi bir yanıt alıp alamayacağımı göreceğim.
SunSparc

1
Geliştirici desteği için 29 $ ödemeniz gerekebilir. Bu, bir kişinin ne kadar zaman harcadığına bakılmaksızın, herhangi bir işletme için önemsiz bir miktar paradır.
Tim

1
@Tim, geliştirici desteğinin sadece 29 $ olmadığını unutmayın. Bu temel fiyat. Aylık AWS faturanızın% 3'ü> = 29 $ ise, üs yerine% 3 ödersiniz.
Michael - sqlbot

Thanks @ Michael-sqlbot, bunun farkında değildim. Ayrılmış örnekler gibi şeyler olduğunda destek fiyatlarının hızlı bir şekilde toplanabileceğini biliyorum, ancak çok fazla kaynağa sahip olduğunuzda geliştirici fiyatlarına hiç bakmadım.
Tim
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.