[英]Cloudfront CORS issue serving fonts on Rails application
訪問我的網站時,我不斷從控制台收到此錯誤消息:
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.
我已經嘗試了一切:
配置 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
但沒有,零,納達。
我在 Heroku 上使用 Rails 4.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提供。 我的應用程序在“www”上響應http和https。 和區域頂點,沒有進行任何重定向。
我簡要地看了一下問題中提到的font_assets gem,但很快就把它放在了機架上,這似乎更有意義。 我不想簡單地打開所有的起源和所有路徑,因為這會破壞CORS的點和同源策略的安全性,所以我需要能夠指定我允許的幾個起源。 最后,我個人贊成通過個人config/initializers/*.rb
文件配置Rails,而不是編輯標准配置文件(如config.ru
或config/application.rb
)將所有這些放在一起,這是我的解決方案,我相信是最好的,截至2016-04-16:
的Gemfile
gem "rack-cors"
rack-cors gem在Rack中間件中實現CORS協議。 除了在已批准的源上設置Access-Control-Allow-Origin和相關標頭之外,它還添加了一個Vary: Origin
響應標頭,指示CloudFront分別緩存每個源的響應(包括響應標頭)。 當我們的網站可以通過多個來源(例如通過http和https,以及通過“www。”和裸域)訪問時,這是至關重要的
配置/初始化/機架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應用程序(而不是代表malicious-site.com)訪問我們的Rails應用程序(以及擴展,在CloudFront上,因為它正在鏡像我們)上的資源,並且僅用於/assets/
urls(而不是我們的控制器)。 換句話說,允許CloudFront為資產提供服務,但不要再開門了。
筆記:
起源列表也可以作為Regexps完成。 小心將模式錨定在字符串末尾。
origins [ /\\Ahttps?:\\/\\/(www\\.)?example\\.com\\z/, /\\Ahttps?:\\/\\/example-staging\\.herokuapp\\.com\\z/ ]
但我認為只讀文字字符串會更容易。
配置CloudFront將瀏覽器的Origin請求標頭傳遞給我們的Rails應用程序。
奇怪的是,看起來CloudFront將Origin標頭從瀏覽器轉發到我們的Rails應用程序, 無論我們是否在此處添加它,但CloudFront僅在將Origin明確添加到標題白名單時才會尊重我們的應用程序的Vary: Origin
緩存指令(截至2016年4月) )。
請求標題白名單有點埋沒。
如果分發已存在,您可以在以下位置找到它:
如果您尚未創建分發版,請在以下位置創建:
單擊創建分發
(為了完整性和可重復性,我列出了我從默認設置更改的所有設置,但白名單設置是唯一與此討論相關的設置)
交付方式:Web(不是RTMP)
原點設置
默認緩存行為設置
更改所有這些內容后,請記住,任何舊的緩存值都可能需要一些時間才能從CloudFront到期。 您可以通過轉到CloudFront分配的“失效”選項卡並為*
創建失效來明確使緩存的資產無效。
如果您在Passenger和Heroku上運行Rails :(如果沒有,請直接跳到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
請注意,我直接ping服務器而不通過CDN,CDN然后在使所有內容無效之后應該轉發服務器響應的任何內容。 這里重要的一行是Server: nginx/1.10.0
,它表示資產由nginx而不是Rails提供。 因此, rack-cors
配置不適用。
適用於我們的解決方案如下: http : //monksealsoftware.com/ruby-on-rails-cors-heroku-passenger-5-0-28/
它主要涉及克隆和修改Passenger的nginx配置文件,這並不理想,因為每次乘客升級和模板更改時都需要維護此副本。
===
這是一個總結:
導航到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
我剛才遇到了同樣的問題,並設法解決了這個問題。
您已正確告知Cloudfront允許這些標頭,但您尚未將這些標頭添加到Cloudfront獲取字體的位置。 是的,您的原始標題是允許的,但Heroku不會發送帶有字體的標題。
要解決此問題,您需要在Heroku上添加適當的CORS標頭。 幸運的是,這很容易。
首先,將rack/cors
gem添加到您的項目中。 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
從5.0版開始,Rails允許為資產設置自定義HTTP標頭,您不必使用rack-cors或font-assets gem。 要為資產(包括字體)設置Access-Control-Allow-Origin,只需將以下代碼添加到config / environments / 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上的任何設置。
這是一個repo,使用適用於Heroku的Rails 5.2提供自定義字體。 它進一步優化了根據https://www.webpagetest.org/盡可能快地提供字體服務
https://github.com/nzoschke/edgecors
app/assets/fonts
@font-face
聲明放在scss文件中並使用font-url
helper 來自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;
}
我正在使用CloudFront,添加了Heroku Edge插件 。
如果您使用自己的CloudFront,請確保將其配置為將瀏覽器Origin
標頭轉發到您的后端源。
首先在production.rb
配置CDN前綴和默認的Cache-Control
標頭:
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
如果您嘗試訪問從herokuapp.com URL到CDN URL的字體,您的瀏覽器中將出現CORS錯誤:
來自' https://edgecors.herokuapp.com '的' https://d1unsc88mkka3m.cloudfront.net/assets/Inconsolata-Regular.ttf '訪問字體已被CORS政策阻止:沒有'訪問控制 - 允許-Origin'標題出現在請求的資源上。 edgecors.herokuapp.com/ GET https://d1unsc88mkka3m.cloudfront.net/assets/Inconsolata-Regular.ttf net :: ERR_FAILED
因此,配置CORS以允許訪問從Heroku到CDN URL的字體:
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
資產管道構建.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
最終結果是從長期存在的CloudFront緩存提供的app/assets/fonts
的自定義字體文件。
可能最好的是使用rack-cors
gem。 本着自己動手和跟進@GeekJock 的回答的精神。 如果不想使用rack-cors
gem,這是一個可憐人的 CORS 標頭處理,例如我們只關心靜態字體資產(例如替換font_assets
過時的 gem)。
就像在另一個答案中一樣,您輸入了應用程序配置:
config.public_file_server.headers = {
'Access-Control-Allow-Origin' => '*'
}
要處理OPTIONS
飛行前請求,您可以在/lib
下的某處編寫路由匹配器:
module FontAssetsConstraint
FONT_EXTENSIONS = %w[eot svg ttf otf woff woff2].freeze
module_function
def matches?(request)
extension = request.params["format"]
extension.present? && FONT_EXTENSIONS.include?(extension)
end
end
然后添加一個路由定義config/routes.rb
來捕捉那些要回復的:
Rails.application.routes.draw do
# Respond to pre-flight CSRF requests for font assets
if Rails.configuration.public_file_server.enabled &&
Rails.configuration.public_file_server.headers.include?("Access-Control-Allow-Origin")
constraints FontAssetsConstraint do
match "*path", via: :options, to: ->(hash) { [204, Rails.configuration.public_file_server.headers, []] }
end
end
除了編寫路由匹配器和定義之外,您還可以創建自己的中間件來捕獲字體:
class AssetsOptionsResponder
TYPES = %w(eot svg ttf otf woff woff2).freeze
def initialize(app)
@app = app
end
def call(env)
if env["REQUEST_METHOD"] == "OPTIONS" && targeted?(env["PATH_INFO"])
[204, access_control_headers, []]
else
@app.call(env)
end
end
private
def targeted?(pathinfo)
return if pathinfo.blank?
TYPES.include? extension(pathinfo)
end
def extension(pathinfo)
pathinfo.split("?").first.split(".").last
end
def access_control_headers
Rails.configuration.public_file_server.headers
end
end
然后在應用程序配置或初始化程序中,您可以添加此中間件:
Rails.application.configure do
if defined?(ActionDispatch::Static) &&
Rails.configuration.public_file_server.enabled &&
Rails.configuration.public_file_server.headers.include?("Access-Control-Allow-Origin")
config.middleware.insert_before ActionDispatch::Static, AssetsOptionsResponder
end
end
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.