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。

这是一个非常难以处理的问题,原因有两个:

  1. CloudFront正在镜像我们的Rails应用程序的响应标头这一事实需要您扭转局面。 CORS协议很难理解,但现在你必须在两个层面上遵循它:浏览器和CloudFront之间(当我们的Rails应用程序将它用作CDN时)​​,以及浏览器和我们的Rails应用程序之间(当一些恶意网站想要滥用我们)。

    CORS实际上是关于浏览器和网页想要访问的第三方资源之间的对话。 (在我们的用例中,这就是CloudFront CDN,为我们的应用程序提供资产。)但是,由于CloudFront 我们的应用程序获取其Access-Control响应标头我们的应用程序需要提供这些标头,就好像它是CloudFront通话, 同时不是授予权限,使其自身暴露于导致首先开发同源策略/ CORS的滥用类型。 特别是,我们不应授予*访问我们网站上的*资源。

  2. 我发现了那么多过时的信息 – 无穷无尽的博客文章和SO线程。 自从许多post以来,CloudFront已经大大改善了其CORS支持,尽管它仍然不完美。 (CORS应该是开箱即用的。)gem本身也在不断发展。

我的设置:在Heroku上运行Rails 4.1.15,资源由CloudFront提供。 我的应用程序在“www”上响应http和https。 和区域顶点,没有进行任何重定向。

我简要地看了一下问题中提到的font_assets gem,但很快就把它放在了机架上,这似乎更有意义。 我不想简单地打开所有的起源和所有路径,因为这会破坏CORS的点和同源策略的安全性,所以我需要能够指定我允许的几个起源。 最后,我个人赞成通过个人config/initializers/*.rb文件配置Rails而不是编辑标准配置文件(如config.ruconfig/application.rb )将所有这些放在一起,这是我的解决方案,我相信是最好的,截至2016-04-16:

  1. 的Gemfile

     gem "rack-cors" 

    rack-cors gem在Rack中间件中实现CORS协议。 除了在已批准的源上设置Access-Control-Allow-Origin和相关标头之外,它还添加了一个Vary: Origin响应标头,指示CloudFront分别缓存每个源的响应(包括响应标头)。 当我们的网站可以通过多个来源访问时(例如通过http和https,以及通过“www。”和裸域),这一点至关重要

  2. 配置/初始化/机架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/ ] 

      但我认为只读文字字符串会更容易。

  3. 配置CloudFront将浏览器的Origin请求标头传递给我们的Rails应用程序。

    奇怪的是,看起来CloudFront将Origin标头从浏览器转发到我们的Rails应用程序, 无论我们是否在此处添加它,但CloudFront仅在Origin明确添加到标题白名单时才会尊重我们的应用程序的Vary: Origin缓存指令(截至2016年4月) )。

    请求标题白名单有点埋没。

    如果分发已存在,您可以在以下位置找到它:

    如果您尚未创建分发,请在以下位置创建:

    • 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