Cloudfront CORS问题在Rails应用程序上提供字体

Man*_* F. 23 fonts ruby-on-rails heroku cors amazon-cloudfront

访问我的网站时,我一直收到来自控制台的错误消息:

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.
Run Code Online (Sandbox Code Playgroud)

我尝试了一切:

但没有,零,虚无...

我在Heroku上使用Rails 4.1.

Noa*_*man 67

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

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

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

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

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

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

  1. 的Gemfile

    gem "rack-cors"
    
    Run Code Online (Sandbox Code Playgroud)

    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
    
    Run Code Online (Sandbox Code Playgroud)

    这告诉浏览器它可以仅代表我们的Rails应用程序(而不是代表malicious-site.com)访问我们的Rails应用程序(以及扩展,在CloudFront上,因为它正在镜像我们)上的资源,并且仅用于/assets/URL (而不是我们的控制器).换句话说,允许CloudFront提供资产,但不要再开门了.

    笔记:

    • 我尝试机架超时之后插入它,而不是在中间件链的头部.尽管拥有相同的中间件(除了Honeybadger),它仍然可以在dev上工作,但并未参与Heroku.
    • 起源列表也可以作为Regexps完成.小心将模式锚定在字符串末尾.

      origins [
          /\Ahttps?:\/\/(www\.)?example\.com\z/,
          /\Ahttps?:\/\/example-staging\.herokuapp\.com\z/
      ]
      
      Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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


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

    • https://console.aws.amazon.com/cloudfront/home#distributions
    • 单击创建分发

      (为了完整性和可重复性,我列出了我从默认设置更改的所有设置,但白名单设置是唯一与此讨论相关的设置)

    • 交付方式:Web(不是RTMP)

    • 原点设置

      • 来源域名:example.com
      • 原始SSL协议:仅限TLSv1.2
      • 原始协议策略:仅限HTTPS
    • 默认缓存行为设置

      • 查看器协议策略:将HTTP重定向到HTTPS
      • 转发标题:白名单
      • 白名单标题:选择Origin并单击添加>>
      • 自动压缩对象:是的

更改所有这些内容后,请记住,任何旧的缓存值都可能需要一些时间才能从CloudFront到期.您可以通过转到CloudFront分配的"失效"选项卡并为其创建失效来明确使缓存的资产无效*.

  • @Noah:这是我在那里找到的最佳答案.非常感谢你...你已经解决了我的问题. (5认同)
  • 这应该标记为正确答案.为了提供帮助,亚马逊显然已经在"基于选定请求标头的缓存"下隐藏了更多"前向标题"选项 (5认同)
  • 用curl测试它:`curl -H"原产地:https://example.com" - 我https:// example.com/assets/your-font.ttf` (2认同)

mig*_*igu 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
Run Code Online (Sandbox Code Playgroud)

请注意,我直接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
Run Code Online (Sandbox Code Playgroud)

打开config/passenger_config.erb并评论此行

<%# include_passenger_internal_template('rails_asset_pipeline.erb', 8, false) %>
Run Code Online (Sandbox Code Playgroud)

在上面提到的行下面添加这些配置

### 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 ###
Run Code Online (Sandbox Code Playgroud)

更改Procfile以包括此自定义配置文件

web: bundle exec passenger start -p $PORT --max-pool-size 2 --nginx-config-template ./config/passenger_config.erb
Run Code Online (Sandbox Code Playgroud)

然后部署......

===

如果您知道更好的解决方案,请填写评论.

实现后,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
Run Code Online (Sandbox Code Playgroud)


Gee*_*ock 5

从 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' => '*'
}
Run Code Online (Sandbox Code Playgroud)

标头值也可以是特定域,如下所示:

config.public_file_server.headers = {
  'Access-Control-Allow-Origin' => 'https://www.example.org'
}
Run Code Online (Sandbox Code Playgroud)

这适用于我的应用程序,我不需要更改 Cloudfront 上的任何设置。


小智 2

我刚刚遇到了同样的问题并设法解决了它。

您已正确告知 Cloudfront 允许这些标头,但您尚未将这些标头添加到 Cloudfront 获取字体的位置。是的,您的原始标头是允许的,但 Heroku 无论如何都不会使用字体发送这些标头。

要解决此问题,您需要将正确的 CORS 标头添加到 Heroku 上的字体中。幸运的是,这很容易。

首先,将rack/corsgem 添加到您的项目中。 https://github.com/cyu/rack-cors

接下来,配置您的机架服务器以为其服务的任何资产加载和配置 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
Run Code Online (Sandbox Code Playgroud)

这将从 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
Run Code Online (Sandbox Code Playgroud)