Je continue à recevoir ce message d'erreur de la console lorsque je visite mon site 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.
J'ai tout essayé:
configuré le fichier application.rb
config.font_assets.Origin = 'http://example.com'
En-têtes de liste blanche sur Cloudfront, comme expliqué dans cet article à
Access-Control-Allow-Origin
Access-Control-Allow-Methods
Access-Control-Allow-Headers
Access-Control-Max-Age
Mais rien, zéro, nada.
J'utilise Rails 4.1 sur Heroku.
C'était une question incroyablement difficile à traiter, pour deux raisons:
Le fait que CloudFront soit en miroir les en-têtes de réponse de notre application Rails vous oblige à changer vos idées. Le protocole CORS est assez difficile à comprendre, mais vous devez maintenant le suivre à deux niveaux: entre le navigateur et CloudFront (lorsque notre application Rails l’utilise en tant que CDN), et entre le navigateur et notre application Rails (lorsque un site malveillant veut abuser de nous).
CORS consiste en réalité en un dialogue entre le navigateur et les ressources tierces auxquelles une page Web souhaite accéder. (Dans notre cas d'utilisation, il s'agit du CDN CloudFront, qui sert des actifs à notre application.) Mais depuis que CloudFront obtient ses en-têtes de réponse Access-Control de notre application, notre application doit les servir comme if c'est CloudFront qui parle, et simultanément n'accorde pas d'autorisations qui s'exposeraient au type d'abus ayant conduit à la création de la stratégie de même origine/CORS. En particulier, nous ne devrions pas accorder un accès *
aux ressources *
de notre site.
J'ai trouvé so much des informations obsolètes - une ligne infinie de billets de blog et de fils SO. CloudFront a considérablement amélioré la prise en charge de CORS depuis plusieurs de ces publications, même si elle n’est toujours pas parfaite. (La SCRO devrait vraiment être traitée immédiatement). Et les pierres précieuses elles-mêmes ont évolué.
Ma configuration: Rails 4.1.15 s'exécutant sur Heroku, avec des actifs servis depuis CloudFront. Mon application répond à la fois à http et à https, à la fois sur "www." et le sommet de la zone, sans aucune redirection.
J'ai brièvement regardé la pierre précieuse de font_assets mentionnée dans la question, mais je l'ai rapidement abandonnée au profit de rack-cors, ce qui semblait plus pertinent. Je ne voulais pas simplement ouvrir toutes les origines et tous les chemins, car cela irait à l'encontre de l'objectif de la SCRO et de la sécurité de la politique de la même origine. Je devais donc pouvoir spécifier les quelques origines que je permettrais. Enfin, je préfère personnellement configurer Rails via des fichiers config/initializers/*.rb
individuels plutôt que de modifier les fichiers de configuration standard (tels que config.ru
ou config/application.rb
). En résumé, voici ma solution, qui, à mon avis, est la meilleure disponible, à compter du 2016-04-16:
Gemfile
gem "rack-cors"
La gem rack-cors implémente le protocole CORS dans un middleware Rack. En plus de définir des en-têtes Access-Control-Allow-Allow et les environnements associés sur les origines approuvées, il ajoute un en-tête de réponse Vary: Origin
, en ordonnant à CloudFront de mettre en cache les réponses. (y compris les en-têtes de réponse) pour chaque origine séparément. Ceci est crucial lorsque notre site est accessible via plusieurs origines (par exemple via http et https, et via "www." Et le domaine nu).
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
Cela indique au navigateur qu'il peut accéder aux ressources de notre application Rails (et par extension, à CloudFront, car il nous reflète) uniquement pour le compte de notre application Rails (et pas au nom du site malveillant. com) et uniquement pour les URL /assets/
(et non pour nos contrôleurs). En d'autres termes, autorisez CloudFront à gérer des actifs mais n'ouvrez pas la porte plus que nécessaire.
Remarques:
La liste des origines peut aussi être faite sous forme de regexps. Veillez à ancrer les motifs en fin de chaîne.
origins [
/\Ahttps?:\/\/(www\.)?example\.com\z/,
/\Ahttps?:\/\/example-staging\.herokuapp\.com\z/
]
mais je pense que c’est plus facile de lire des chaînes littérales.
Configurez CloudFront pour transmettre l'en-tête de requête Origin du navigateur à notre application Rails.
Bizarrement, il semble que CloudFront transmette l'en-tête Origin du navigateur à notre application Rails indépendamment de si nous l'ajoutons ici, mais que CloudFront respecte la directive de mise en cache Vary: Origin
de notre application uniquement si Origin est explicitement ajouté à la liste blanche des en-têtes ( avril 2016).
La liste blanche d'en-tête de demande est un peu enterrée.
Si la distribution existe déjà, vous pouvez la trouver à:
Si vous n'avez pas encore créé la distribution, créez-la à l'adresse:
Cliquez sur Créer une distribution
(Par souci d'exhaustivité et de reproductibilité, je liste tous les paramètres que j'ai modifiés par défaut, mais les paramètres de la liste blanche sont les seuls qui sont pertinents pour cette discussion.)
Méthode de livraison: Web (pas RTMP)
Paramètres d'origine
Paramètres de comportement du cache par défaut
Après avoir modifié toutes ces choses, rappelez-vous qu'il peut falloir un certain temps pour que toutes les anciennes valeurs mises en cache expirent à partir de CloudFront. Vous pouvez explicitement invalider des actifs mis en cache en accédant à l'onglet Invalidations de la distribution CloudFront et en créant une invalidation pour *
.
Si vous utilisez Rails on Passenger and Heroku: (sinon, passez directement à la réponse de Noach Magedman)
La réponse de Noach Magedman m'a été très utile pour configurer correctement CloudFront.
J'ai également installé rack-cors
exactement comme décrit et, même si cela fonctionnait bien en développement, les commandes CURL en production ne renvoyaient aucune des configurations 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
Notez que je coche le serveur directement sans passer par le CDN, qui, après avoir invalidé tout le contenu, doit simplement transférer la réponse du serveur. La ligne importante ici est Server: nginx/1.10.0
, ce qui indique que les actifs sont servis par nginx et non par Rails. En conséquence, les configurations rack-cors
ne s'appliquent pas.
La solution qui a fonctionné pour nous est la suivante: http://monksealsoftware.com/Ruby-on-Rails-cors-heroku-passenger-5-0-28/
Il s’agissait essentiellement de cloner et de modifier le fichier de configuration nginx pour Passenger, ce qui n’est pas idéal, car cette copie doit être conservée à chaque fois que Passenger est mis à niveau et que le modèle est modifié.
===
Voici un résumé:
Naviguez jusqu'au dossier racine de votre projet Rails et copiez le modèle de configuration nginx.
cp $(passenger-config about resourcesdir)/templates/standalone/config.erb config/passenger_config.erb
Ouvrez config/passenger_config.erb
et commentez cette ligne
<%# include_passenger_internal_template('Rails_asset_pipeline.erb', 8, false) %>
Ajoutez ces configurations sous la ligne mentionnée ci-dessus
### 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 ###
Changez la Procfile
pour inclure ce fichier de configuration personnalisé.
web: bundle exec passenger start -p $PORT --max-pool-size 2 --nginx-config-template ./config/passenger_config.erb
Alors déployez ...
===
Si vous connaissez une meilleure solution, veuillez inclure les commentaires.
Après la mise en œuvre, la commande CURL a généré la réponse suivante:
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
Je viens d'avoir le même problème et j'ai réussi à le résoudre.
Vous avez correctement demandé à Cloudfront d'autoriser ces en-têtes, mais vous n'avez pas ajouté ces en-têtes à l'endroit où Cloudfront obtient la police. Oui, vos en-têtes Origin sont autorisés, mais Heroku n'envoie pas ces en-têtes avec la police.
Pour résoudre ce problème, vous devez obtenir les en-têtes CORS appropriés ajoutés à la police sur Heroku. Heureusement, c'est assez facile.
Tout d’abord, ajoutez la gemme rack/cors
à votre projet. https://github.com/cyu/rack-cors
Ensuite, configurez votre serveur Rack pour charger et configurer CORS pour tous les actifs qu’il sert. Ajoutez les éléments suivants après le préchargement de votre application dans 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
Ainsi, toutes les ressources renvoyées par Heroku seront appliquées aux en-têtes CORS appropriés. Vous pouvez restreindre l’application des en-têtes en fonction de vos fichiers et de vos besoins en matière de sécurité.
Une fois déployé, accédez à Cloudfront et commencez une invalidation sur tout ce qui vous avait précédemment fourni une erreur d'autorisation CORS. Désormais, lorsque Cloudfront charge une nouvelle copie à partir de Heroku, il possède les en-têtes appropriés. Cloudfront les transmettra au client comme précédemment configuré avec vos autorisations Origin
.
Pour vous assurer de fournir les en-têtes appropriés à partir de votre serveur, vous pouvez utiliser la commande curl suivante pour valider vos en-têtes: curl -I -s -X GET -H "Origin: www.yoursite.com" http://www.yoursite.dev:5000/assets/fonts/myfont.svg
Vous devriez voir les en-têtes suivants renvoyés:
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
Voici un repo qui montre une police personnalisée avec Rails 5.2 qui fonctionne sur Heroku. Il va plus loin et optimise l'utilisation des polices pour qu'elles soient aussi rapides que possible selon https://www.webpagetest.org/
https://github.com/nzoschke/edgecors
app/assets/fonts
@font-face
dans un fichier scss et utilisez l'assistant font-url
De 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;
}
J'utilise CloudFront, ajouté avec l'addon Heroku Edge .
Si vous utilisez votre propre CloudFront, assurez-vous de le configurer pour transférer l'en-tête Origin
du navigateur vers votre origine dorsale.
Configurez d’abord un préfixe CDN et des en-têtes Cache-Control
par défaut dans 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
Si vous essayez d'accéder à la police de l'URL herokuapp.com à l'URL du CDN, vous obtiendrez une erreur CORS dans votre navigateur:
Accès à la police sur ' https://d1unsc88mkka3m.cloudfront.net/assets/Inconsolata-Regular.ttf ' from Origin ' https://edgecors.herokuapp.com ' a été bloqué par la politique CORS : Aucun en-tête 'Access-Control-Allow-Origin' n'est présent sur la ressource demandée. edgecors.herokuapp.com/ GET https://d1unsc88mkka3m.cloudfront.net/assets/Inconsolata-Regular.ttf net :: ERR_FAILED
Configurez donc CORS pour autoriser l’accès à la police de Heroku vers l’URL du CDN:
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
Le pipeline d'actifs crée un fichier .ttf.gz
mais ne le sert pas. Ce patch de singe modifie la liste blanche gzip du pipeline d’actifs en liste noire:
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
Le résultat final est un fichier de police personnalisé dans app/assets/fonts
servi à partir d'un cache CloudFront de longue durée.
À partir de la version 5.0, Rails permet de définir des en-têtes HTTP personnalisés pour les actifs. Vous n'avez pas à utiliser les gems rack-cors ou font-assets. Afin de définir Access-Control-Allow-Origin pour les actifs (y compris les polices), ajoutez simplement le code suivant à config/environment/production.rb:
config.public_file_server.headers = {
'Access-Control-Allow-Origin' => '*'
}
La valeur de l'en-tête pourrait également être un domaine spécifique, tel que le suivant:
config.public_file_server.headers = {
'Access-Control-Allow-Origin' => 'https://www.example.org'
}
Cela a fonctionné pour mon application et je n'ai pas eu besoin de modifier les paramètres sur Cloudfront.