web-dev-qa-db-ja.com

CloudfrontCORSがRailsアプリケーションでフォントを提供する問題

Webサイトにアクセスすると、コンソールから次のエラーメッセージが表示され続けます。

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を使用しています。

23
Manuel F.

これは、次の2つの理由から、対処するのが非常に難しい問題でした。

  1. CloudFrontがミラーリングであるという事実は、Railsアプリの応答ヘッダーであるため、気をひねる必要があります。 CORSプロトコルは、そのままでは理解するのが難しいですが、ブラウザーとCloudFrontの間(RailsアプリがCDNとして使用する場合)とブラウザーの間の2つのレベルで従う必要があります。 Railsアプリ(悪意のあるサイトが私たちを悪用したい場合)。

    CORSは、実際には、ブラウザとWebページがアクセスしたいサードパーティのリソースとの間のダイアログに関するものです。 (私たちのユースケースでは、それはCloudFront CDNであり、アプリのアセットを提供します。)しかし、CloudFrontはAccess-Control応答ヘッダーを取得するためfromアプリなので、アプリはそれらのヘッダーを提供する必要がありますあたかも CloudFrontが話しているので、同時にそもそも同一生成元ポリシー/ CORSの開発につながるタイプの悪用にさらされるようなアクセス許可を付与しないでください。特に、当サイトの*リソースへの*アクセスを許可しないでください。

  2. 私はそこに非常に多くの古い情報を見つけました-ブログ投稿とSOスレッドの無限の行。 CloudFrontは、これらの投稿の多くからCORSサポートを大幅に改善しましたが、まだ完全ではありません。 (CORSは実際にはすぐに処理する必要があります。)そして、宝石自体は進化しました。

私のセットアップ:Rails 4.1.15はHerokuで実行され、アセットはCloudFrontから提供されます。私のアプリは、両方の「www」でhttpとhttpsの両方に応答します。リダイレクトを行わずに、ゾーンの頂点。

質問で言及されているfont_assetsgemを簡単に調べましたが、すぐにそれを削除して、より適切に思われるラックコアを採用しました。 CORSのポイントと同一生成元ポリシーのセキュリティを損なうため、すべてのオリジンとすべてのパスを単純に開くことはしたくありませんでした。そのため、許可するいくつかのオリジンを指定できる必要がありました。最後に、私は個人的に、標準の構成ファイル(config/initializers/*.rbconfig.ruなど)を編集するのではなく、個々のconfig/application.rbファイルを介してRailsを構成することを好みます。 2016年4月16日現在、利用可能な最善のソリューションであると私は信じています。

  1. Gemfile

    gem "rack-cors"
    

    Rack-cors gemは、RackミドルウェアにCORSプロトコルを実装します。承認されたオリジンにAccess-Control-Allow-Originと関連ヘッダーを設定することに加えて、Vary: Origin応答ヘッダーを追加し、CloudFrontに各オリジンの応答(応答ヘッダーを含む)を個別にキャッシュするように指示します。これは、複数のオリジンを介して(たとえば、httpとhttpsの両方を介して、および「www。」とベアドメインの両方を介して)サイトにアクセスできる場合に重要です。

  2. config/initializers/Rack-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アプリ(および拡張すると、CloudFrontはミラーリングしているため)のリソースにアクセスできるのは、Railsアプリ(および- not悪意のある-site.comに代わって)、/assets/ urlの場合のみ(およびnotコントローラーの場合)。つまり、CloudFrontがアセットを提供できるようにしますが、必要以上にドアを開けないでください。

    ノート:

    • ミドルウェアチェーンの先頭ではなく、これを挿入してみましたafter rack-timeout。それは開発で動作しましたが、同じミドルウェア(Honeybadger以外)を持っていたにもかかわらず、Herokuを起動していませんでした。
    • 起源リストは正規表現としても実行できます。文字列の終わりにパターンを固定するように注意してください。

      origins [
          /\Ahttps?:\/\/(www\.)?example\.com\z/,
          /\Ahttps?:\/\/example-staging\.herokuapp\.com\z/
      ]
      

      しかし、リテラル文字列を読むだけの方が簡単だと思います。

  3. ブラウザのOriginリクエストヘッダーをRailsアプリに渡すようにCloudFrontを設定します。

    不思議なことに、CloudFrontはOriginヘッダーをブラウザーからRailsアプリに転送するようです関係なくここに追加するかどうかに関係なく、CloudFrontはアプリのVary: Originキャッシングディレクティブを尊重しますOriginがヘッダーホワイトリストに明示的に追加されている場合のみ(2016年4月現在)。

    リクエストヘッダーのホワイトリストは一種の埋もれています。

    ディストリビューションがすでに存在する場合は、次の場所で見つけることができます。

    • https://console.aws.Amazon.com/cloudfront/home#distributions
    • 分布を選択します
    • [配布設定]をクリックします
    • [動作]タブに移動します
    • 動作を選択します(おそらく1つだけです)
    • [編集]をクリックします
    • 転送ヘッダー:ホワイトリスト
    • ホワイトリストヘッダー:Originを選択し、追加>>


    ディストリビューションをまだ作成していない場合は、次の場所で作成してください。

    • https://console.aws.Amazon.com/cloudfront/home#distributions
    • [配布の作成]をクリックします

      (完全性と再現性のために、デフォルトから変更したすべての設定をリストしていますが、この説明に関連するのはホワイトリスト設定のみです)

    • 配信方法:Web(RTMPではない)

    • オリジン設定

      • オリジンドメイン名:example.com
      • オリジンSSLプロトコル:TLSv1.2のみ
      • オリジンプロトコルポリシー:HTTPSのみ
    • デフォルトのキャッシュ動作設定

      • ビューアープロトコルポリシー:HTTPをHTTPSにリダイレクトする
      • 転送ヘッダー:ホワイトリスト
      • ホワイトリストヘッダー:Originを選択し、追加>>
      • オブジェクトを自動的に圧縮する:はい

これらすべてを変更した後、キャッシュされた古い値がCloudFrontから期限切れになるまでに時間がかかる場合があることに注意してください。 CloudFrontディストリビューションの[Invalidations]タブに移動し、*の無効化を作成することで、キャッシュされたアセットを明示的に無効化できます。

67
Noach Magedman

乗客と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

CDNを経由せずにサーバーに直接pingを実行すると、CDNはすべてのコンテンツを無効にした後、サーバーが応答するものをすべて転送する必要があることに注意してください。ここで重要な行はServer: nginx/1.10.0です。これは、アセットがRailsではなくnginxによって提供されることを示します。結果として、rack-cors構成は適用されません。

私たちのために働いた解決策はここにあります: http://monksealsoftware.com/Ruby-on-Rails-cors-heroku-passenger-5-0-28/

基本的に、Passengerのnginx構成ファイルのクローンを作成して変更する必要がありましたが、Passengerがアップグレードされてテンプレートが変更されるたびにこのコピーを維持する必要があるため、理想的ではありません。

===

ここに要約があります:

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
8
migu

私はちょうど同じ問題を抱えていて、なんとかそれを解決することができました。

これらのヘッダーを許可するようにCloudfrontに正しく指示しましたが、Cloudfrontがフォントを取得する場所にこれらのヘッダーを追加していません。はい、Originヘッダーは許可されていますが、Herokuはそれらのヘッダーをフォントとともに送信していません。

これを修正するには、Herokuのフォントに適切なCORSヘッダーを追加する必要があります。幸いなことに、これは非常に簡単です。

まず、rack/corsgemをプロジェクトに追加します。 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
2
Joe Burns

バージョン5.0以降、RailsではアセットのカスタムHTTPヘッダーを設定でき、rack-corsまたはfont-assetsgemを使用する必要はありません。Access-Control-Allowを設定するために-アセット(フォントを含む)のオリジン。次のコードをconfig/environment /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の設定を変更する必要はありませんでした。

1
GeekJock

これは、Herokuで動作するRails 5.2でカスタムフォントを提供するデモです。さらに進んで、 https:/)に従ってフォントの提供を可能な限り高速にするように最適化します。 /www.webpagetest.org/

https://github.com/nzoschke/edgecors

資産パイプラインとSCSS

  • フォントを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;
}

CORSを使用してCDNから提供

Heroku Edgeアドオン で追加されたCloudFrontを使用しています。

独自のCloudFrontを使用している場合は、ブラウザーのOriginヘッダーをバックエンドのオリジンに転送するように設定してください。

まず、CDNプレフィックスとデフォルトのCache-Controlヘッダーを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.comURLからCDNURLにフォントにアクセスしようとすると、ブラウザでCORSエラーが発生します。

' https://d1unsc88mkka3m.cloudfront.net/assets/Inconsolata-Regular.ttf ' from Origin ' https://edgecors.herokuapp.com 'のフォントへのアクセスCORSポリシーによってブロックされました:「Access-Control-Allow-Origin」ヘッダーがリクエストされたリソースに存在しません。 edgecors.herokuapp.com/ GET https://d1unsc88mkka3m.cloudfront.net/assets/Inconsolata-Regular.ttf net :: ERR_FAILED

したがって、HerokuからCDNURLへのフォントへのアクセスを許可するようにCORSを構成します。

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

Gzipフォントアセットを提供する

アセットパイプラインは.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のカスタムフォントファイルです。

0
Noah Zoschke