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
但没有,零,虚无…
我在Heroku上使用Rails 4.1。
这是一个非常难以处理的问题,原因有两个:
-
CloudFront正在镜像我们的Rails应用程序的响应标头这一事实需要您扭转局面。 CORS协议很难理解,但现在你必须在两个层面上遵循它:浏览器和CloudFront之间(当我们的Rails应用程序将它用作CDN时),以及浏览器和我们的Rails应用程序之间(当一些恶意网站想要滥用我们)。
CORS实际上是关于浏览器和网页想要访问的第三方资源之间的对话。 (在我们的用例中,这就是CloudFront CDN,为我们的应用程序提供资产。)但是,由于CloudFront 从我们的应用程序获取其Access-Control响应标头,我们的应用程序需要提供这些标头,就好像它是CloudFront通话, 同时不是授予权限,使其自身暴露于导致首先开发同源策略/ CORS的滥用类型。 特别是,我们不应授予
*
访问我们网站上的*
资源。 -
我发现了那么多过时的信息 – 无穷无尽的博客文章和SO线程。 自从许多post以来,CloudFront已经大大改善了其CORS支持,尽管它仍然不完美。 (CORS应该是开箱即用的。)gem本身也在不断发展。
我的设置:在Heroku上运行Rails 4.1.15,资源由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提供资产,但不要再开门了。笔记:
- 我尝试在机架超时之后插入它而不是在中间件链的头部。 虽然拥有相同的中间件(Honeybadger除外),但它在开发方面有所作为,但并没有在Heroku上使用。
-
起源列表也可以作为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月) )。请求标题白名单有点埋没。
如果分发已存在,您可以在以下位置找到它:
- https://console.aws.amazon.com/cloudfront/home#distributions
- 选择分配
- 单击分发设置
- 转到“行为”选项卡
- 选择行为(可能只有一个)
- 单击编辑
- 前进标题:白名单
- 白名单标题:选择Origin并单击Add >>
如果您尚未创建分发,请在以下位置创建:
- https://console.aws.amazon.com/cloudfront/home#distributions
-
单击创建分发
(为了完整性和可重复性,我列出了我从默认设置更改的所有设置,但白名单设置是唯一与此讨论相关的设置)
-
交付方式:Web(不是RTMP)
-
原点设置
- 来源域名:example.com
- 原始SSL协议:仅限TLSv1.2
- 原始协议策略:仅限HTTPS
-
默认缓存行为设置
- 查看器协议策略:将HTTP重定向到HTTPS
- 前进标题:白名单
- 白名单标题:选择Origin并单击Add >>
- 自动压缩对象:是的
更改所有这些内容后,请记住,任何旧的缓存值都可能需要一些时间才能从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命令validation标头: 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