Cloudfront CORS выпускает шрифты в приложении Rails
Я продолжаю получать это сообщение об ошибке с консоли при посещении моего веб-сайта:
font from origin 'https://xxx.cloudfront.net' has been blocked from loading by Cross-Origin Resource Sharing policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://www.example.com' is therefore not allowed access.
Я пробовал все:
- Я установил font_assets gem
-
настроил файл application.rb
config.font_assets.origin = 'http://example.com'
-
"Белые заголовки на Cloudfront", как описано в этой статье, чтобы
Access-Control-Allow-Origin
Access-Control-Allow-Methods
Access-Control-Allow-Headers
Access-Control-Max-Age
Но ничего, ноль, нада.
Я использую Rails 4.1 на Heroku.
Ответы
Ответ 1
Это была невероятно трудная проблема для решения по двум причинам:
-
Тот факт, что CloudFront зеркалирует, наши заголовки ответов приложений Rails требуют, чтобы вы крутили свой разум. Протокол CORS достаточно прост, чтобы понять его, но теперь вы должны следовать ему на двух уровнях: между браузером и CloudFront (когда наше приложение Rails использует его как CDN), так и между браузером и нашим Rails-приложением (когда какой-то вредоносный сайт хочет нас оскорбить).
CORS действительно представляет собой диалог между браузером и сторонними ресурсами, к которым хочет получить доступ веб-страница. (В нашем прецеденте, это CloudFront CDN, обслуживающий активы для нашего приложения.) Но поскольку CloudFront получает заголовки ответов Access-Control из нашего приложения, наше приложение должно обслуживать эти заголовки, как если бы речь шла о CloudFront, и одновременно не предоставить разрешения на выдачу разрешений, которые будут подвергать себя жестокому обращению, которое привело бы к тому, что в первую очередь разрабатывалась политика одинакового происхождения /CORS. В частности, мы не должны предоставлять *
доступ к ресурсам *
на нашем сайте.
-
Я нашел так много устаревшей информации - бесконечную строку сообщений в блогах и SO-потоков. CloudFront значительно улучшил поддержку CORS, так как многие из этих сообщений, хотя и по-прежнему не идеальны. (CORS действительно нужно обрабатывать из коробки). И сами драгоценные камни эволюционировали.
Моя настройка: Rails 4.1.15 работает на Heroku, с активами, обслуживаемыми CloudFront. Мое приложение отвечает как на http, так и на https, как на "www". и вершину зоны, без какого-либо перенаправления.
Я коротко взглянул на драгоценный камень font_assets, упомянутый в вопросе, но быстро отбросил его в пользу стойки-корса, что казалось более точным. Я не хотел просто открывать все происхождение и все пути, так как это победит точку CORS и безопасность политики одинакового происхождения, поэтому мне нужно было указать несколько истоков, которые я бы позволил. Наконец, я лично предпочитаю настраивать Rails через отдельные файлы config/initializers/*.rb
, а не редактировать стандартные файлы конфигурации (например, config.ru
или config/application.rb
). Объединяя все это вместе, вот мое решение, которое, по моему мнению, является лучшим из доступных 2016-04-16:
-
Gemfile
gem "rack-cors"
Драгоценный камень стойки-корса реализует протокол CORS в промежуточном ПО Rack.
В дополнение к настройке Access-Control-Allow-Origin и связанных заголовков на одобренных источниках, он добавляет заголовок ответа Vary: Origin
, направляя CloudFront кэшировать ответы (включая заголовки ответов) для каждого источника отдельно. Это важно, когда наш сайт доступен через несколько источников (например, через HTTP и HTTPS, а также через "www." И "голый домен" )
-
Config/инициализаторы/стойку cors.rb
## Configure Rack CORS Middleware, so that CloudFront can serve our assets.
## See https://github.com/cyu/rack-cors
if defined? Rack::Cors
Rails.configuration.middleware.insert_before 0, Rack::Cors do
allow do
origins %w[
https://example.com
http://example.com
https://www.example.com
http://www.example.com
https://example-staging.herokuapp.com
http://example-staging.herokuapp.com
]
resource '/assets/*'
end
end
end
Это говорит браузеру, что он может получить доступ к ресурсам в нашем Rails-приложении (и, в добавлении, на CloudFront, поскольку он отражает нас) только от имени нашего Rails-приложения (а не от имени malicious-site.com) и только для /assets/
URL (а не для наших контроллеров). Другими словами, позволить CloudFront обслуживать активы, но не открывать дверь больше, чем мы должны.
Примечания:
- Я попытался вставить это после тайм-аута стойки, а не во главе цепи промежуточного программного обеспечения.
Он работал над разработчиком, но не стал пинать на Heroku, несмотря на
с тем же промежуточным программным обеспечением (кроме Honeybadger).
-
Список истоков также может быть выполнен как Regexps.
Будьте осторожны, чтобы привязать шаблоны к концу строки.
origins [
/\Ahttps?:\/\/(www\.)?example\.com\z/,
/\Ahttps?:\/\/example-staging\.herokuapp\.com\z/
]
но я думаю, что его проще просто читать литералы.
-
Конфигурировать CloudFront для передачи заголовка запроса заголовка браузера в наше приложение Rails.
Странно, похоже, что CloudFront пересылает заголовок Origin из браузера в наше приложение Rails, независимо от того, добавляем ли мы его здесь, но CloudFront чтит наши приложения Vary: Origin
директива кэширования только в том случае, если Origin явно добавлен в белый список заголовков (как от апреля 2016 года).
Белый список заголовка запроса похож на захороненный.
Если распределение уже существует, вы можете найти его по адресу:
- https://console.aws.amazon.com/cloudfront/home#distributions
- выберите дистрибутив
- нажмите Настройки распространения
- перейдите на вкладку "Поведение"
- выберите поведение (возможно, будет только один)
- Нажмите "Изменить"
- Передовые заголовки: белый список
- Заголовки белых списков: Выберите Происхождение и нажмите Добавить →
Если вы еще не создали дистрибутив, создайте его по адресу:
- https://console.aws.amazon.com/cloudfront/home#distributions
-
Нажмите "Создать дистрибутив"
(Для полноты и воспроизводимости я перечисляю все настройки, которые я изменил с значений по умолчанию, однако настройки Whitelist являются единственными, которые имеют отношение к этому обсуждению)
-
Способ доставки: Web (не RTMP)
-
Настройки источника
- Происхождение Имя домена: example.com
- Протоколы SSL происхождения: ТОЛЬКО TLSv1.2
- Политика протокола происхождения: только HTTPS
-
Настройки поведения кэша по умолчанию
- Политика протокола просмотра: перенаправление HTTP на HTTPS
- Передовые заголовки: белый список
- Заголовки белых списков: Выберите Происхождение и нажмите Добавить →
- Автоматическое сжатие объектов: Да
Изменив все эти вещи, помните, что может потребоваться некоторое время для истечения срока действия старых, кешированных значений из CloudFront. Вы можете явно аннулировать кешированные активы, перейдя на вкладку Invalidations на вкладке CloudFront и создав недействительность для *
.
Ответ 2
Если вы запустите Rails на Пассажире и Heroku: (если нет, перейдите прямо к ответу Noach Magedman)
Ответ Noach Magedman был очень полезен для меня, чтобы правильно настроить CloudFront.
Я также установил rack-cors
точно так, как описано, и хотя он отлично работал в разработке, команды CURL в производстве никогда не возвращали ни одну из конфигураций CORS:
curl -H "Origin: https://tidyme-staging.com.au" -I http://tidyme-staging.com.au/assets/31907B_4_0-588bd4e720d4008295dcfb85ef36b233ee0817d7fe23c76a3a543ebba8e7c85a.ttf
HTTP/1.1 200 OK
Connection: keep-alive
Server: nginx/1.10.0
Date: Wed, 03 Aug 2016 00:29:37 GMT
Content-Type: application/x-font-ttf
Content-Length: 316664
Last-Modified: Fri, 22 Jul 2016 03:31:57 GMT
Expires: Thu, 31 Dec 2037 23:55:55 GMT
Cache-Control: max-age=315360000
Cache-Control: public
Accept-Ranges: bytes
Via: 1.1 vegur
Обратите внимание, что я пинговаю сервер напрямую, не пропуская CDN, CDN после того, как недействительный весь контент должен просто перенаправлять все, что отвечает сервер. Важная строка здесь Server: nginx/1.10.0
, что указывает на то, что активы обслуживаются nginx, а не Rails. Как следствие, конфигурации rack-cors
не применяются.
Решение, которое сработало для нас, находится здесь: http://monksealsoftware.com/ruby-on-rails-cors-heroku-passenger-5-0-28/
В основном это касалось клонирования и изменения файла конфигурации nginx для Passenger, что не является идеальным, так как эта копия должна поддерживаться каждый раз, когда Passenger обновляется и изменяется шаблон.
===
Здесь резюме:
Перейдите в корневую папку проекта Rails и создайте копию шаблона конфигурации nginx
cp $(passenger-config about resourcesdir)/templates/standalone/config.erb config/passenger_config.erb
Откройте config/passenger_config.erb
и прокомментируйте эту строку
<%# include_passenger_internal_template('rails_asset_pipeline.erb', 8, false) %>
Добавьте эти конфигурации ниже указанной выше строки
### BEGIN your own configuration options ###
# This is a good place to put your own config
# options. Note that your options must not
# conflict with the ones Passenger already sets.
# Learn more at:
# https://www.phusionpassenger.com/library/config/standalone/intro.html#nginx-configuration-template
location ~ "^/assets/.+\.(woff|eot|svg|ttf|otf).*" {
error_page 490 = @static_asset_fonts;
error_page 491 = @dynamic_request;
recursive_error_pages on;
if (-f $request_filename) {
return 490;
}
if (!-f $request_filename) {
return 491;
}
}
# Rails asset pipeline support.
location ~ "^/assets/.+-([0-9a-f]{32}|[0-9a-f]{64})\..+" {
error_page 490 = @static_asset;
error_page 491 = @dynamic_request;
recursive_error_pages on;
if (-f $request_filename) {
return 490;
}
if (!-f $request_filename) {
return 491;
}
}
location @static_asset {
gzip_static on;
expires max;
add_header Cache-Control public;
add_header ETag "";
}
location @static_asset_fonts {
gzip_static on;
expires max;
add_header Cache-Control public;
add_header ETag "";
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, HEAD, OPTIONS';
add_header 'Access-Control-Allow-Headers' '*';
add_header 'Access-Control-Max-Age' 3628800;
}
location @dynamic_request {
passenger_enabled on;
}
### END your own configuration options ###
Измените Procfile
, чтобы включить этот настраиваемый файл конфигурации
web: bundle exec passenger start -p $PORT --max-pool-size 2 --nginx-config-template ./config/passenger_config.erb
Затем разверните...
===
Если вы знаете лучшее решение, добавьте комментарии.
После реализации команда CURL дала следующий ответ:
curl -H "Origin: https://tidyme-staging.com.au" -I http://tidyme-staging.com.au/assets/31907B_4_0-588bd4e720d4008295dcfb85ef36b233ee0817d7fe23c76a3a543ebba8e7c85a.ttf
HTTP/1.1 200 OK
Connection: keep-alive
Server: nginx/1.10.0
Date: Wed, 03 Aug 2016 01:43:48 GMT
Content-Type: application/x-font-ttf
Content-Length: 316664
Last-Modified: Fri, 22 Jul 2016 03:31:57 GMT
Expires: Thu, 31 Dec 2037 23:55:55 GMT
Cache-Control: max-age=315360000
Cache-Control: public
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, HEAD, OPTIONS
Access-Control-Allow-Headers: *
Access-Control-Max-Age: 3628800
Accept-Ranges: bytes
Via: 1.1 vegur
Ответ 3
У меня была одна и та же проблема, и мне удалось ее решить.
Вы правильно сказали Cloudfront разрешить эти заголовки, но вы не добавили эти заголовки туда, где Cloudfront получает шрифт. Да, ваши заголовки происхождения разрешены, но Heroku не отправляет эти заголовки с шрифтом в любом случае.
Чтобы исправить это, вам нужно будет добавить правильные заголовки CORS к шрифту на Heroku. К счастью, это довольно легко.
Сначала добавьте драгоценный камень rack/cors
в свой проект. https://github.com/cyu/rack-cors
Затем настройте сервер Rack для загрузки и настройки CORS для любых активов, которые он обслуживает. Добавьте следующее после предварительной загрузки приложения в config.ru
require 'rack/cors'
use Rack::Cors do
allow do
origins '*'
resource '/cors',
:headers => :any,
:methods => [:post],
:credentials => true,
:max_age => 0
resource '*',
:headers => :any,
:methods => [:get, :post, :delete, :put, :patch, :options, :head],
:max_age => 0
end
end
Это устанавливает любые ресурсы, возвращенные из Heroku, чтобы иметь соответствующие заголовки CORS. Вы можете ограничить применение заголовков в зависимости от вашего файла и требований безопасности.
После развертывания перейдите в Cloudfront и начните аннулирование на все, что ранее давало вам ошибку разрешения CORS. Теперь, когда Cloudfront загружает новую копию из Heroku, у нее будут правильные заголовки, а Cloudfront передаст эти заголовки клиенту, как ранее настроенные с вашими разрешениями Origin
.
Чтобы убедиться, что вы обслуживаете соответствующие заголовки с вашего сервера, вы можете использовать следующую команду curl для проверки своих заголовков:
curl -I -s -X GET -H "Origin: www.yoursite.com" http://www.yoursite.dev:5000/assets/fonts/myfont.svg
Вы должны увидеть следующие возвращенные заголовки:
Access-Control-Allow-Origin: www.yoursite.com
Access-Control-Allow-Methods: GET, POST, DELETE, PUT, PATCH, OPTIONS, HEAD
Access-Control-Max-Age: 0
Access-Control-Allow-Credentials: true
Ответ 4
Начиная с версии 5.0, Rails позволяет устанавливать настраиваемые заголовки HTTP для ресурсов, и вам не нужно использовать гем-стойку или шрифты-ресурсы. Чтобы установить Access-Control-Allow-Origin для ресурсов (включая шрифты), просто добавьте следующий код в config/environment/production.rb:
config.public_file_server.headers = {
'Access-Control-Allow-Origin' => '*'
}
Значением заголовка также может быть конкретный домен, например:
config.public_file_server.headers = {
'Access-Control-Allow-Origin' => 'https://www.example.org'
}
Это работало для моего приложения, и мне не нужно было менять какие-либо настройки в Cloudfront.
Ответ 5
Вот репозиторий, демонстрирующий обслуживание пользовательского шрифта в Rails 5.2, который работает на Heroku. Это идет дальше и оптимизирует обслуживание шрифтов, чтобы быть максимально быстрым согласно https://www.webpagetest.org/
https://github.com/nzoschke/edgecors
Asset Pipeline и SCSS
- Поместите шрифты в
app/assets/fonts
- Поместите объявление
@font-face
в файл scss и используйте помощник font-url
Из app/assets/stylesheets/welcome.scss
:
@font-face {
font-family: 'Inconsolata';
src: font-url('Inconsolata-Regular.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
body {
font-family: "Inconsolata";
font-weight: bold;
}
Подавать из CDN с CORS
Я использую CloudFront, добавленный с помощью дополнения Heroku Edge.
Если вы используете свой собственный CloudFront, не забудьте настроить его так, чтобы он перенаправлял заголовок браузера Origin
к исходному источнику.
Сначала настройте префикс CDN и заголовки Cache-Control
умолчанию в production.rb
:
Rails.application.configure do
# e.g. https://d1unsc88mkka3m.cloudfront.net
config.action_controller.asset_host = ENV["EDGE_URL"]
config.public_file_server.headers = {
'Cache-Control' => 'public, max-age=31536000'
}
end
Если вы попытаетесь получить доступ к шрифту с URL-адреса herokuapp.com по URL-адресу CDN, вы получите сообщение об ошибке CORS в вашем браузере:
Доступ к шрифту по адресу https://d1unsc88mkka3m.cloudfront.net/assets/Inconsolata-Regular.ttf из источника " https://edgecors.herokuapp.com " был заблокирован политикой CORS: нет "Access-Control-Allow" Заголовок -Origin 'присутствует на запрашиваемом ресурсе. edgecors.herokuapp.com/ПОЛУЧИТЕ https://d1unsc88mkka3m.cloudfront.net/assets/Inconsolata-Regular.ttf net :: ERR_FAILED
Поэтому настройте CORS, чтобы разрешить доступ к шрифту из Heroku по URL CDN:
module EdgeCors
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 5.2
config.middleware.insert_after ActionDispatch::Static, Rack::Deflater
config.middleware.insert_before 0, Rack::Cors do
allow do
origins %w[
http://edgecors.herokuapp.com
https://edgecors.herokuapp.com
]
resource "*", headers: :any, methods: [:get, :post, :options]
end
end
end
end
Служить gzip Font Asset
Конвейер ресурсов создает файл .ttf.gz
но не обслуживает его. Этот патч обезьяны изменяет белый список gzip конвейера ресурсов на черный список:
require 'action_dispatch/middleware/static'
ActionDispatch::FileHandler.class_eval do
private
def gzip_file_path(path)
return false if ['image/png', 'image/jpeg', 'image/gif'].include? content_type(path)
gzip_path = "#{path}.gz"
if File.exist?(File.join(@root, ::Rack::Utils.unescape_path(gzip_path)))
gzip_path
else
false
end
end
end
Конечным результатом является файл пользовательских шрифтов в app/assets/fonts
подается из долгоживущего кеша CloudFront.