访问我的网站时,我一直收到来自控制台的错误消息:
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上的白名单标题,如this article至
中所述Access-Control-Allow-Origin
Access-Control-Allow-Methods
Access-Control-Allow-Headers
Access-Control-Max-Age
但没有,零,纳达。
我在Heroku上使用Rails 4.1。
答案 0 :(得分:65)
这是一个非常难以处理的问题,原因有两个:
CloudFront 镜像我们的Rails应用程序的响应标题这一事实要求您扭转思绪。 CORS协议很难理解,但现在你必须在两个层面上遵循它:浏览器和CloudFront之间(当我们的Rails应用程序将它用作CDN时),以及浏览器和我们的Rails应用程序之间(当一些恶意网站想滥用我们。)
CORS实际上是关于浏览器和网页想要访问的第三方资源之间的对话。 (在我们的用例中,这是CloudFront CDN,为我们的应用程序提供资产。)但是,由于CloudFront从我们的应用程序获取其访问控制响应标头,我们的应用程序需要提供这些标题好像是CloudFront正在谈话,并且同时没有授予权限,这会使自己暴露于导致首先开发同源策略/ CORS的滥用类型。特别是,我们不应授予*
访问我们网站上*
资源的权限。
我发现那么多过时的信息 - 博客帖子和SO帖子无穷无尽。自从许多帖子以来,CloudFront已经显着改进了其CORS支持,尽管它仍然不完美。 (CORS应该是开箱即用的。)宝石本身也在不断发展。
我的设置:在Heroku上运行的Rails 4.1.15,其资产来自CloudFront。我的应用程序响应http和https,在" www。"和区域顶点,没有进行任何重定向。
我简要地看了一下问题中提到的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,以及通过&#34; www。&#34;以及裸域)
<强>配置/初始化/机架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应用程序(以及不代表我们的Rails应用程序(以及扩展,在CloudFront上,因为它正在镜像我们)访问资源) malicious-site.com)并且仅适用于/assets/
网址(而不是适用于我们的控制器)。换句话说,允许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月)。
请求标题白名单有点埋没。
如果分发已存在,您可以在以下位置找到它:
如果您尚未创建分发版,请在以下位置创建:
单击“创建分发”
(为了完整性和可重复性,我列出了我从默认设置更改的所有设置,但白名单设置是唯一与此讨论相关的设置)
投放方式:网络(非RTMP)
原点设置
默认缓存行为设置
更改所有这些内容后,请记住,任何旧的缓存值都可能需要一些时间才能从CloudFront过期。您可以通过转到CloudFront分配的“无效”标签并为*
创建无效来明确使缓存的资产无效。
答案 1 :(得分:8)
如果您在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
答案 2 :(得分:2)
我遇到了同样的问题,并设法解决了这个问题。
您已正确告知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
答案 3 :(得分:0)
从5.0版开始,Rails允许为资产设置自定义HTTP标头,而您不必使用rack-cors或font-assets宝石。为了为资产(包括字体)设置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上更改任何设置。
答案 4 :(得分:0)
这里是一个回购,演示了如何在Heroku上使用Rails 5.2提供自定义字体。它进一步完善并根据https://www.webpagetest.org/
优化了字体的投放速度https://github.com/nzoschke/edgecors
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;
}
我正在使用CloudFront,并添加了Heroku Edge addon。
如果您使用自己的CloudFront,请确保将其配置为将浏览器Origin
标头转发到您的后端源。
首先在Cache-Control
中配置CDN前缀和默认的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
如果您尝试从herokuapp.com URL到CDN URL访问字体,则会在浏览器中收到CORS错误:
CORS政策禁止从来源“ https://d1unsc88mkka3m.cloudfront.net/assets/Inconsolata-Regular.ttf”访问“ https://edgecors.herokuapp.com”的字体: 所请求的资源上没有“ Access-Control-Allow-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
最终的结果是app/assets/fonts
中的自定义字体文件是由长期存在的CloudFront缓存提供的。