Sous-domaines, ports et protocoles génériques Access-Control-Allow-Origin


312

J'essaie d'activer CORS pour tous les sous-domaines, ports et protocoles.

Par exemple, je veux pouvoir exécuter une demande XHR de http://sub.mywebsite.com:8080/ à https://www.mywebsite.com/ *

En règle générale, j'aimerais activer la demande de correspondance d'origine (et limitée à):

//*.mywebsite.com:*/*

Réponses:


207

Sur la base de la réponse de DaveRandom , je jouais également et j'ai trouvé une solution Apache légèrement plus simple qui produit le même résultat ( Access-Control-Allow-Originest définie dynamiquement sur le protocole + domaine + port spécifique actuel) sans utiliser de règles de réécriture:

SetEnvIf Origin ^(https?://.+\.mywebsite\.com(?::\d{1,5})?)$   CORS_ALLOW_ORIGIN=$1
Header append Access-Control-Allow-Origin  %{CORS_ALLOW_ORIGIN}e   env=CORS_ALLOW_ORIGIN
Header merge  Vary "Origin"

Et c'est tout.

Ceux qui souhaitent activer CORS sur le domaine parent (par exemple mywebsite.com) en plus de tous ses sous-domaines peuvent simplement remplacer l'expression régulière de la première ligne par celle-ci:

^(https?://(?:.+\.)?mywebsite\.com(?::\d{1,5})?)$.

Remarque: pour la conformité aux spécifications et un comportement de mise en cache correct, TOUJOURS ajouter l'en- Vary: Origintête de réponse pour les ressources compatibles CORS, même pour les demandes non CORS et celles provenant d'une origine non autorisée (voir exemple pourquoi ).


1
Nous avions presque cela (pas Vary Origin) et avons eu un mauvais comportement lorsque les visiteurs ont sauté entre plusieurs sous-domaines en utilisant la même police. La police et l'en-tête d'origine du contrôle d'accès ont également été mis en cache. J'ai fait un petit changement sur celui-ci: j'utilise "Access-Control-Allow-Origin *" si la demande provient d'un de nos domaines autorisés. Peut-être que cela a été résolu avec "Vary Origin" que nous n'avions pas auparavant ... maintenant ajouté cela aussi.
Erik Melkersson

2
Ne fonctionne pas pour le domaine principal 'mywebsite.com'
biology.info

1
@pgmann, il n'est pas nécessaire d'échapper à //dans ce contexte, car la configuration Apache n'utilise pas d'expressions régulières délimitées par des barres obliques . Regexr se plaint parce que, dans ce contexte, les barres obliques ont une signification particulière en tant que délimiteurs.
Noyo

1
The 'Access-Control-Allow-Origin' header contains multiple values '^(https?://(?:.+.)?aerofotea.com(?::d{1,5})?)$', but only one is allowed. Origin 'http://local.aerofotea.com' is therefore not allowed access.
Aero Wang

3
Où placez-vous ce code? .htaccess ou dans la configuration de l'hôte virtuel apache?
Glen

252

La spécification CORS est tout ou rien. Il ne supporte que *, nullou le protocole exact + domaine + port: http://www.w3.org/TR/cors/#access-control-allow-origin-response-header

Votre serveur devra valider l'en-tête d'origine à l'aide de l'expression régulière, puis vous pourrez faire écho à la valeur d'origine dans l'en-tête de réponse Access-Control-Allow-Origin.


7
@Dexter "null" peut être utilisé en réponse à une origine "null", par exemple lors d'une requête CORS à partir d'un schéma file: //.
monsur

130
Il est profondément myope que la spécification CORS ne prendrait pas en charge le cas d'utilisation exact du PO .
2014

6
@aroth: Pas vraiment, la spécification permet des implémentations d'utiliser une syntaxe de correspondance qu'ils veulent; il ne serait que profondément à courte vue qu'une mise en œuvre ne soutienne pas ce cas d'utilisation. En d'autres termes, ce que vous spécifiez à votre serveur n'est pas la valeur ACAO, ce dernier n'est qu'un détail de protocole. Mon hypothèse est qu'il existe un scénario de sécurité qui nécessite ou bénéficie de l'écho de l'origine, mais une implémentation naïve fonctionne simplement en disant "OK" ou non.
TNE

3
Mise à jour de 2015: cette réponse doit être considérée comme nuisible, car elle est incomplète et peut entraîner des problèmes de mise en cache. Veuillez consulter ma réponse ci-dessous pour une implémentation correcte (pour Apache) et une explication: stackoverflow.com/a/27990162/357774 . De plus, @aroth, comme le souligne tne, la spécification permet en fait le cas d'utilisation exact de l'OP: w3.org/TR/cors/#resource-implementation . Et comme le souligne cette réponse, c'est au serveur de l'implémenter. Cela peut se faire en 3 lignes, comme le montre la réponse mentionnée ci-dessus.
Noyo

19
@Noyo - Je clarifierai alors ma signification d'origine. Il est profondément myope que la spécification CORS n'exige pas strictement que tous les serveurs qui implémentent CORS fournissent une prise en charge automatique intégrée pour le cas d'utilisation exact de l'OP . Laisser à chaque utilisateur le soin de créer son propre shim en utilisant du code PHP personnalisé, des règles de réécriture ou ce que vous avez est une recette pour la fragmentation, les bogues et les catastrophes. Les développeurs de serveurs devraient savoir mieux que cela; et s'ils ne le font pas, la spécification CORS devrait les y forcer.
2015

59

EDIT : Utilisez la solution de @ Noyo au lieu de celle-ci. C'est plus simple, plus clair et probablement beaucoup plus performant sous charge.

RÉPONSE ORIGINALE GAUCHE ICI À DES FINS HISTORIQUES UNIQUEMENT !!


J'ai joué un peu avec ce problème et j'ai trouvé cette solution réutilisable .htaccess (ou httpd.conf) qui fonctionne avec Apache:

<IfModule mod_rewrite.c>
<IfModule mod_headers.c>
    # Define the root domain that is allowed
    SetEnvIf Origin .+ ACCESS_CONTROL_ROOT=yourdomain.com

    # Check that the Origin: matches the defined root domain and capture it in
    # an environment var if it does
    RewriteEngine On
    RewriteCond %{ENV:ACCESS_CONTROL_ROOT} !=""
    RewriteCond %{ENV:ACCESS_CONTROL_ORIGIN} =""
    RewriteCond %{ENV:ACCESS_CONTROL_ROOT}&%{HTTP:Origin} ^([^&]+)&(https?://(?:.+?\.)?\1(?::\d{1,5})?)$
    RewriteRule .* - [E=ACCESS_CONTROL_ORIGIN:%2]

    # Set the response header to the captured value if there was a match
    Header set Access-Control-Allow-Origin %{ACCESS_CONTROL_ORIGIN}e env=ACCESS_CONTROL_ORIGIN
</IfModule>
</IfModule>

Définissez simplement la ACCESS_CONTROL_ROOTvariable en haut du bloc sur votre domaine racine et elle fera écho de la Origin:valeur d'en-tête de la demande au client dans la Access-Control-Allow-Origin:valeur d'en-tête de réponse si elle correspond à votre domaine.

Notez également que vous pouvez utiliser en sub.mydomain.comtant que ACCESS_CONTROL_ROOTet cela limitera les origines à sub.mydomain.comet *.sub.mydomain.com(c'est-à-dire qu'il ne doit pas nécessairement être la racine du domaine). Les éléments autorisés à varier (protocole, port) peuvent être contrôlés en modifiant la partie de correspondance URI de l'expression régulière.


22

Je réponds à cette question, car la réponse acceptée ne peut pas suivre

  1. Le regroupement de regex est un succès de performance , ce qui n'est pas nécessaire.
  2. ne peut pas correspondre au domaine principal et ne fonctionne que pour le sous-domaine.

Par exemple: il n'enverra pas d'en-têtes CORS pour http://mywebsite.com alors qu'il fonctionne pour http://somedomain.mywebsite.com/

SetEnvIf Origin "http(s)?://(.+\.)?mywebsite\.com(:\d{1,5})?$" CORS=$0

Header set Access-Control-Allow-Origin "%{CORS}e" env=CORS
Header merge  Vary "Origin"

Pour activer votre site, il vous suffit de mettre votre site à la place de "mywebsite.com" dans la configuration Apache ci-dessus.

Pour autoriser plusieurs sites:

SetEnvIf Origin "http(s)?://(.+\.)?(othersite\.com|mywebsite\.com)(:\d{1,5})?$" CORS=$0

Test après le déploiement:

La réponse curl suivante doit avoir l'en-tête "Access-Control-Allow-Origin" après la modification.

curl -X GET -H "Origin: http://examplesite1.com" --verbose http://examplesite2.com/query

12

J'avais besoin d'une solution PHP uniquement, donc juste au cas où quelqu'un en aurait également besoin. Il prend une chaîne d'entrée autorisée comme "* .example.com" et renvoie le nom du serveur d'en-tête de demande, si l'entrée correspond.

function getCORSHeaderOrigin($allowed, $input)
{
    if ($allowed == '*') {
        return '*';
    }

    $allowed = preg_quote($allowed, '/');

    if (($wildcardPos = strpos($allowed, '*')) !== false) {
        $allowed = str_replace('*', '(.*)', $allowed);
    }

    $regexp = '/^' . $allowed . '$/';

    if (!preg_match($regexp, $input, $matches)) {
        return 'none';
    }

    return $input;
}

Et voici les cas de test pour un fournisseur de données phpunit:

//    <description>                            <allowed>          <input>                   <expected>
array('Allow Subdomain',                       'www.example.com', 'www.example.com',        'www.example.com'),
array('Disallow wrong Subdomain',              'www.example.com', 'ws.example.com',         'none'),
array('Allow All',                             '*',               'ws.example.com',         '*'),
array('Allow Subdomain Wildcard',              '*.example.com',   'ws.example.com',         'ws.example.com'),
array('Disallow Wrong Subdomain no Wildcard',  '*.example.com',   'example.com',            'none'),
array('Allow Double Subdomain for Wildcard',   '*.example.com',   'a.b.example.com',        'a.b.example.com'),
array('Don\'t fall for incorrect position',    '*.example.com',   'a.example.com.evil.com', 'none'),
array('Allow Subdomain in the middle',         'a.*.example.com', 'a.bc.example.com',       'a.bc.example.com'),
array('Disallow wrong Subdomain',              'a.*.example.com', 'b.bc.example.com',       'none'),
array('Correctly handle dots in allowed',      'example.com',     'exampleXcom',            'none'),

1
+1, modifié pour l'utiliser preg_quote()parce que c'est la bonne façon de le faire (même si .le seul méta-caractère regexp valide dans un nom DNS preg_quote()décrit mieux l'opération prévue)
DaveRandom

1
Il convient de préciser qu'il nonene s'agit pas d'une valeur sémantiquement valide pour l'en-tête (ou du moins, ne fait pas ce qu'elle implique) selon la spécification. En tant que tel, return null;peut avoir plus de sens pour cette branche, et dans ce cas, aucun en-tête ne doit être envoyé au client, il doit donc être vérifié par l'appelant.
DaveRandom

preg_quote()cite le signe * et ainsi, str_replace()par exemple, laisse un "\" orphelin.
Christoffer Bubach

1
c'est utile, j'ai passé du temps sur le problème CORS jusqu'à ce que je réalise que mon site avait "www" dans l'ajax, mais pas dans la structure de permalien - votre solution m'a aidé à comprendre où était le problème et l'a résolu pour moi.
Sol

3

Lors de la configuration Access-Control-Allow-Originen .htaccess, seuls les éléments suivants ont fonctionné:

SetEnvIf Origin "http(s)?://(.+\.)?domain\.com(:\d{1,5})?$" CRS=$0
Header always set Access-Control-Allow-Origin "%{CRS}e" env=CRS

J'ai essayé plusieurs autres mots - clés suggérés Header append, Header set, aucun travaillé comme suggéré dans de nombreuses réponses sur le SO, bien que je ne sais pas si ces mots - clés sont obsolètes ou non valables pour nginx .

Voici ma solution complète:

SetEnvIf Origin "http(s)?://(.+\.)?domain\.com(:\d{1,5})?$" CRS=$0
Header always set Access-Control-Allow-Origin "%{CRS}e" env=CRS
Header merge Vary "Origin"

Header always set Access-Control-Allow-Methods "GET, POST"
Header always set Access-Control-Allow-Headers: *

# Cached for a day
Header always set Access-Control-Max-Age: 86400

RewriteEngine On

# Respond with 200OK for OPTIONS
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]

2

Nous avions des problèmes similaires avec Font Awesome sur un domaine statique "sans cookie" lors de la lecture des polices du "domaine des cookies" (www.domain.tld) ​​et ce message était notre héros. Voir ici: Comment puis-je résoudre le problème de police Web «En-tête de réponse CORS (Missing Cross-Origin Resource Sharing) manquant»?

Pour les types copier / coller-r (et pour donner quelques accessoires), j'ai reconstitué cela à partir de toutes les contributions et l'ai ajouté en haut du fichier .htaccess de la racine du site:

<IfModule mod_headers.c>
 <IfModule mod_rewrite.c>
    SetEnvIf Origin "http(s)?://(.+\.)?(othersite\.com|mywebsite\.com)(:\d{1,5})?$" CORS=$0
    Header set Access-Control-Allow-Origin "%{CORS}e" env=CORS
    Header merge  Vary "Origin"
 </IfModule>
</IfModule>

Super sécurisé, super élégant. J'adore: vous n'avez pas à ouvrir la bande passante de vos serveurs aux voleurs de ressources / types de liens directs.

Props à: @Noyo @DaveRandom @ pratap-koritala

(J'ai essayé de laisser cela comme un commentaire à la réponse acceptée, mais je ne peux pas encore le faire)



0

Il semble que la réponse d'origine soit antérieure à Apache 2.4. Ça n'a pas marché pour moi. Voici ce que j'ai dû changer pour le faire fonctionner en 2.4. Cela fonctionnera pour n'importe quelle profondeur de sous-domaine de yourcompany.com .

SetEnvIf Host ^((?:.+\.)*yourcompany\.com?)$    CORS_ALLOW_ORIGIN=$1
Header append Access-Control-Allow-Origin  %{REQUEST_SCHEME}e://%{CORS_ALLOW_ORIGIN}e    env=CORS_ALLOW_ORIGIN
Header merge  Vary "Origin"

0

J'ai dû modifier un peu la réponse de Lars , car un orphelin \s'est retrouvé dans l'expression régulière, pour ne comparer que l'hôte réel (sans prêter attention au protocole ou au port) et je voulais prendre en charge un localhostdomaine en plus de mon domaine de production. J'ai donc changé le $allowedparamètre pour être un tableau.

function getCORSHeaderOrigin($allowed, $input)
{
    if ($allowed == '*') {
        return '*';
    }

    if (!is_array($allowed)) {
        $allowed = array($allowed);
    }

    foreach ($allowed as &$value) {
        $value = preg_quote($value, '/');

        if (($wildcardPos = strpos($value, '\*')) !== false) {
            $value = str_replace('\*', '(.*)', $value);
        }
    }

    $regexp = '/^(' . implode('|', $allowed) . ')$/';

    $inputHost = parse_url($input, PHP_URL_HOST);

    if ($inputHost === null || !preg_match($regexp, $inputHost, $matches)) {
        return 'none';
    }

    return $input;
}

Utilisation comme suit:

if (isset($_SERVER['HTTP_ORIGIN'])) {
    header("Access-Control-Allow-Origin: " . getCORSHeaderOrigin(array("*.myproduction.com", "localhost"), $_SERVER['HTTP_ORIGIN']));
}

0

dans mon cas en utilisant angulaire

dans mon intercepteur HTTP, je mets

with Credentials: true.

dans l'en-tête de la demande

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.