Sottodomini, porte e protocolli jolly Access-Control-Allow-Origin


312

Sto cercando di abilitare CORS per tutti i sottodomini, le porte e il protocollo.

Ad esempio, voglio essere in grado di eseguire una richiesta XHR da http://sub.mywebsite.com:8080/ a https://www.mywebsite.com/ *

In genere, vorrei abilitare la richiesta dalle origini corrispondente (e limitata a):

//*.mywebsite.com:*/*

Risposte:


207

Sulla base della risposta di DaveRandom , stavo anche giocando e ho trovato una soluzione Apache leggermente più semplice che produce lo stesso risultato ( Access-Control-Allow-Originè impostato sull'attuale protocollo specifico + dominio + porta in modo dinamico) senza usare alcuna regola di riscrittura:

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"

E questo è tutto.

Coloro che desiderano abilitare CORS sul dominio principale (ad esempio mywebsite.com) oltre a tutti i suoi sottodomini possono semplicemente sostituire l'espressione regolare nella prima riga con questa:

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

Nota: per la conformità delle specifiche e il corretto comportamento della memorizzazione nella cache, aggiungere SEMPRE l' Vary: Originintestazione di risposta per le risorse abilitate per CORS, anche per le richieste non CORS e quelle da un'origine non consentita (vedi esempio perché ).


1
Abbiamo avuto quasi tutto questo (non Vary Origin) e abbiamo avuto cattivi comportamenti quando i visitatori hanno saltato tra più sottodomini utilizzando lo stesso carattere. Anche il font e l'intestazione access-control-origin sono stati memorizzati nella cache. Ho apportato una piccola modifica a questo: utilizzo "Access-Control-Allow-Origin *" se la richiesta proviene da uno dei nostri domini consentiti. Forse questo è stato risolto con "Vary Origin" che non avevamo prima ... ora aggiunto anche quello.
Erik Melkersson,

2
Non funziona per il dominio principale "mywebsite.com"
biology.info

1
@pgmann, non è necessario sfuggire //a questo contesto, poiché la conf Apache non usa espressioni regolari delimitate da barra. Regexr si lamenta perché, in quel contesto, le barre hanno un significato speciale come delimitatori.
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
Dove si inserisce questo codice? .htaccess o nella configurazione dell'host virtuale di apache?
Glen,

252

Le specifiche CORS sono tutto o niente. Supporta solo *,null o il protocollo esatto + dominio + porta: http://www.w3.org/TR/cors/#access-control-allow-origin-response-header

Il tuo server dovrà convalidare l'intestazione di origine usando regex, quindi puoi fare eco al valore di origine nell'intestazione di risposta Access-Control-Allow-Origin.


7
@Dexter "null" può essere utilizzato in risposta a un'origine "null", ad esempio quando si effettua una richiesta CORS da un file: // schema.
mons

130
È profondamente miope che le specifiche CORS non supporteranno l' esatto caso d'uso dell'OP .
aroth

6
@aroth: non proprio, le specifiche consentono alle implementazioni di utilizzare qualsiasi sintassi corrispondente che desiderano; sarebbe solo profondamente miope per un'implementazione che non supporta questo caso d'uso. In altre parole, ciò che specifichi sul tuo server non è il valore ACAO, quest'ultimo è solo un dettaglio del protocollo. La mia ipotesi è che esiste uno scenario di sicurezza che richiede o beneficia del ritorno all'origine, ma un'implementazione ingenua funziona semplicemente dicendo "OK" o no.
TNE

3
Aggiornamento dal 2015: questa risposta dovrebbe essere considerata dannosa, poiché è incompleta e può causare problemi di memorizzazione nella cache. Si prega di consultare la mia risposta di seguito per un'implementazione corretta (per Apache) e una spiegazione: stackoverflow.com/a/27990162/357774 . Inoltre, @aroth, come TNE sottolinea, la specifica realtà non consentire esatto caso d'uso del PO: w3.org/TR/cors/#resource-implementation . E come sottolinea questa risposta, spetta al server implementarla. Questo può essere fatto in 3 righe, come si vede nella risposta di cui sopra.
Noyo,

19
@Noyo - Chiarirò il mio significato originale allora. È profondamente miope che le specifiche CORS non richiedono rigorosamente tutti i server che implementano CORS per fornire supporto integrato automatico per l' esatto caso d'uso dell'OP . Lasciare a ogni singolo utente la possibilità di costruire il proprio shim usando il codice PHP personalizzato, riscrivere le regole o what-have-you è una ricetta per frammentazione, bug e disastri. Gli sviluppatori di server dovrebbero sapere meglio di così; e in caso contrario, le specifiche CORS dovrebbero costringerli a farlo.
aroth,

59

EDIT : utilizzare la soluzione di @ Noyo invece di questa. È più semplice, più chiaro e probabilmente molto più performante sotto carico.

RISPOSTA ORIGINALE LASCIATA QUI SOLO PER SCOPI STORICI !!


Ho provato a risolvere questo problema e ho trovato questa soluzione riutilizzabile .htaccess (o httpd.conf) che funziona con 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>

Basta impostare la ACCESS_CONTROL_ROOTvariabile nella parte superiore del blocco sul dominio principale e Origin:restituirà il valore dell'intestazione della richiesta al client nelAccess-Control-Allow-Origin: valore dell'intestazione risposta se corrisponde al dominio.

Nota anche che puoi usare sub.mydomain.comcome ACCESS_CONTROL_ROOTe limiterà le origini a sub.mydomain.come *.sub.mydomain.com(cioè non deve essere la radice del dominio). Gli elementi che possono variare (protocollo, porta) possono essere controllati modificando la porzione di corrispondenza URI del regex.


22

Sto rispondendo a questa domanda, perché la risposta accettata non può seguire

  1. il raggruppamento regex è un successo prestazionale , che non è necessario.
  2. non può corrispondere al dominio primario e funziona solo per il sottodominio.

Ad esempio: non invierà intestazioni CORS per http://mywebsite.com mentre funziona per 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"

Per abilitare il tuo sito, devi semplicemente mettere il tuo sito al posto di "mywebsite.com" nella configurazione di Apache sopra.

Per consentire più siti:

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

Test dopo la distribuzione:

La seguente risposta all'arricciatura dovrebbe avere l'intestazione "Access-Control-Allow-Origin" dopo la modifica.

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

12

Avevo bisogno di una soluzione solo per PHP, quindi anche nel caso qualcuno ne avesse bisogno. Prende una stringa di input consentita come "* .example.com" e restituisce il nome del server dell'intestazione della richiesta, se l'input corrisponde.

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;
}

Ed ecco i casi di test per un fornitore di dati 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, modificato per l'uso preg_quote()perché è il modo corretto di farlo (anche se .è l'unico meta char regexp valido in un nome DNS, preg_quote()descrive meglio l'operazione prevista)
DaveRandom

1
Va chiarito che nonenon è un valore semanticamente valido per l'intestazione (o almeno non fa ciò che implica) in base alle specifiche. Pertanto, return null;potrebbe avere più senso per quel ramo e in tal caso nessuna intestazione deve essere inviata al client, quindi dovrebbe essere verificata dal chiamante.
DaveRandom,

preg_quote()citerà il segno * e quindi, str_replace()ad esempio, lascia un "\" orfano.
Christoffer Bubach,

1
questo è utile, ho trascorso del tempo sul problema CORS fino a quando ho capito che il mio sito aveva "www" nell'ajax, ma non nella struttura del permalink - la tua soluzione mi ha aiutato a capire dove fosse il problema e risolverlo per me.
Sol,

3

Quando si imposta Access-Control-Allow-Originin .htaccess, ha funzionato solo quanto segue:

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

Ho provato diverse altre parole chiave suggerite Header append, Header set, nessuno ha lavorato come suggerito in molte risposte su così, anche se non ho idea se queste parole chiave sono obsoleti o non validi per nginx .

Ecco la mia soluzione completa:

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

Abbiamo riscontrato problemi simili con Font Awesome su un dominio "senza cookie" statico durante la lettura dei caratteri dal "dominio dei cookie" (www.domain.tld) ​​e questo post è stato il nostro eroe. Vedi qui: Come posso risolvere il problema webfont "Intestazione risposta risposta tra origini mancanti (CORS) mancante?"

Per i tipi copy / paste-r (e per dare alcuni oggetti di scena) ho messo insieme questo da tutti i contributi e l'ho aggiunto all'inizio del file .htaccess della radice del sito:

<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 sicuro, super elegante. Lo adoro: non è necessario aprire la larghezza di banda dei server per i ladri di risorse / tipi hot-link-er.

Puntelli a: @Noyo @DaveRandom @ pratap-koritala

(Ho provato a lasciare questo come commento alla risposta accettata, ma non posso ancora farlo)



0

Sembra che la risposta originale fosse pre Apache 2.4. Non ha funzionato per me. Ecco cosa ho dovuto cambiare per farlo funzionare in 2.4. Questo funzionerà per qualsiasi profondità del sottodominio di 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

Ho dovuto modificare un po 'la risposta di Lars , quando un orfano è \finito nella regex, per confrontare solo l'host reale (non prestando attenzione al protocollo o alla porta) e volevo supportare il localhostdominio oltre al mio dominio di produzione. Quindi ho modificato il $allowedparametro in un array.

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;
}

Utilizzo come segue:

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

0

nel mio caso usando angolare

nel mio intercettore HTTP, ho impostato

with Credentials: true.

nell'intestazione della richiesta

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.