Subdomeny, porty i protokoły Access-Control-Allow-Origin

312

Próbuję włączyć CORS dla wszystkich poddomen, portów i protokołu.

Na przykład chcę mieć możliwość uruchomienia żądania XHR z http://sub.mywebsite.com:8080/ do https://www.mywebsite.com/ *

Zazwyczaj chciałbym włączyć żądanie z dopasowania źródeł (i ograniczone do):

//*.mywebsite.com:*/*

Elie
źródło

Odpowiedzi:

207

Opierając się na odpowiedzi DaveRandoma , również się pobawiłem i znalazłem nieco prostsze rozwiązanie Apache, które daje ten sam rezultat ( Access-Control-Allow-Originjest ustawiony na bieżący konkretny protokół + domena + port dynamicznie) bez użycia reguł przepisywania:

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"

I to wszystko.

Ci, którzy chcą włączyć CORS w domenie nadrzędnej (np. Mywebsite.com) oprócz wszystkich swoich subdomen, mogą po prostu zastąpić wyrażenie regularne w pierwszym wierszu tym:

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

Uwaga: W celu zapewnienia zgodności ze specyfikacją i prawidłowego zachowania w pamięci podręcznej ZAWSZE dodaj Vary: Originnagłówek odpowiedzi dla zasobów obsługujących CORS, nawet dla żądań innych niż CORS i tych z niedozwolonego źródła (patrz przykład dlaczego ).

Noyo
źródło
1
Mieliśmy prawie to (nie zmienne pochodzenie) i otrzymaliśmy złe zachowanie, gdy użytkownicy przeskakiwali między wieloma subdomenami przy użyciu tej samej czcionki. Czcionka i nagłówek kontroli dostępu również zostały buforowane. Wprowadziłem niewielką zmianę w tym: używam „Access-Control-Allow-Origin *”, jeśli żądanie pochodzi z jednej z naszych dozwolonych domen. Być może zostało to rozwiązane za pomocą „Vary Origin”, której wcześniej nie mieliśmy ... teraz też to dodał.
Erik Melkersson,
2
Nie działa dla głównej domeny „mywebsite.com”
biology.info,
1
@pgmann, nie ma potrzeby ucieczki od //tego w tym kontekście, ponieważ conf Apache nie używa wyrażeń regularnych rozdzielanych ukośnikiem. Regexr narzeka, ponieważ w tym kontekście ukośniki mają specjalne znaczenie jako ograniczniki.
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
Gdzie umieszczasz ten kod? .htaccess czy w konfiguracji hosta wirtualnego apache?
Glen
252

Specyfikacja CORS jest „wszystko albo nic”. Obsługuje tylko *, nullczy domena dokładny protokół + port: http://www.w3.org/TR/cors/#access-control-allow-origin-response-header

Twój serwer będzie musiał sprawdzić poprawność nagłówka źródłowego za pomocą wyrażenia regularnego, a następnie możesz powtórzyć wartość początkową w nagłówku odpowiedzi Access-Control-Allow-Origin.

monsur
źródło
7
@Dexter „null” może być użyte w odpowiedzi na „zerowe” pochodzenie, np. Przy składaniu żądania CORS ze schematu file: //.
monsur
130
Bardzo krótkowzroczne jest to, że specyfikacja CORS nie obsługuje dokładnego przypadku użycia OP .
aroth
6
@aroth: Nie bardzo, specyfikacja pozwala implementacjom na użycie dowolnej pasującej składni; byłoby bardzo krótkowzroczne, aby wdrożenie nie wspierało tego przypadku użycia. Innymi słowy, to, co podajesz swojemu serwerowi, nie jest wartością ACAO, ta ostatnia jest jedynie szczegółem protokołu. Zakładam, że istnieje scenariusz bezpieczeństwa, który wymaga echa pochodzenia lub korzysta z niego, ale naiwna implementacja działa po prostu mówiąc „OK” lub nie.
tne
3
Aktualizacja z 2015 r .: odpowiedź należy uznać za szkodliwą, ponieważ jest niepełna i może prowadzić do problemów z buforowaniem. Zobacz moją odpowiedź poniżej, aby uzyskać poprawną implementację (dla Apache) i wyjaśnienie: stackoverflow.com/a/27990162/357774 . Również @aroth, jak tne wskazuje spec rzeczywiście nie pozwalają dokładnie sprawę użyć OP: w3.org/TR/cors/#resource-implementation . Jak wskazuje ta odpowiedź, wdrożenie tego zależy od serwera. Można to zrobić w 3 wierszach, jak widać w odpowiedzi, o której mowa powyżej.
Noyo,
19
@Noyo - wyjaśnię wtedy moje pierwotne znaczenie. Jest bardzo krótkowzroczne, że specyfikacja CORS nie wymaga ściśle od wszystkich serwerów, które implementują CORS, zapewnienia automatycznej, wbudowanej obsługi dokładnego przypadku użycia OP . Pozostawienie każdemu indywidualnemu użytkownikowi zbudowania własnego podkładu przy użyciu niestandardowego kodu PHP, przepisywania reguł lub tego, co masz, to przepis na fragmentację, błędy i katastrofy. Twórcy serwerów powinni wiedzieć lepiej; a jeśli nie, specyfikacja CORS powinna je zmusić.
aroth
59

EDYCJA : Użyj rozwiązania @ Noyo zamiast tego. Jest prostszy, jaśniejszy i prawdopodobnie o wiele bardziej wydajny pod obciążeniem.

ORYGINALNA ODPOWIEDŹ POZOSTAŁA TUTAJ TYLKO DO CELÓW HISTORYCZNYCH !!


Rozmyślałem nad tym problemem i opracowałem to rozwiązanie wielokrotnego użytku .htaccess (lub httpd.conf), które działa z 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>

Wystarczy ustawić ACCESS_CONTROL_ROOTzmienną w górnej części bloku na domenę główną, a ona wyśle ​​echo Origin:wartość nagłówka żądania z powrotem do klienta w Access-Control-Allow-Origin:wartości nagłówka odpowiedzi, jeśli pasuje do twojej domeny.

Zauważ też, że możesz użyć sub.mydomain.comjako ACCESS_CONTROL_ROOTi to ograniczy początki do sub.mydomain.comi *.sub.mydomain.com(tj. Nie musi to być katalog główny domeny). Elementy, które mogą się zmieniać (protokół, port) mogą być kontrolowane przez modyfikację części wyrażenia regularnego URI wyrażenia regularnego.

DaveRandom
źródło
22

Odpowiadam na to pytanie, ponieważ zaakceptowana odpowiedź nie może wykonać następujących czynności

  1. grupowanie wyrażeń regularnych jest hitem wydajności , który nie jest konieczny.
  2. nie może pasować do domeny podstawowej i działa tylko dla subdomeny.

Na przykład: nie wysyła nagłówków CORS dla http://mywebsite.com, podczas gdy działa dla 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"

Aby włączyć witrynę, wystarczy umieścić ją w miejscu „mywebsite.com” w powyższej konfiguracji Apache.

Aby zezwolić na wiele witryn:

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

Testowanie po wdrożeniu:

Następująca odpowiedź na curl powinna mieć nagłówek „Access-Control-Allow-Origin” po zmianie.

curl -X GET -H "Origin: http://examplesite1.com" --verbose http://examplesite2.com/query
Pratap Koritala
źródło
12

Potrzebowałem rozwiązania tylko dla PHP, więc na wypadek, gdyby ktoś również go potrzebował. Pobiera dozwolony ciąg wejściowy, taki jak „* .example.com” i zwraca nazwę serwera nagłówka żądania, jeśli dane wejściowe są zgodne.

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

A oto przypadki testowe dla dostawcy danych 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'),
Lars
źródło
1
+1, edytowane w celu użycia, preg_quote()ponieważ jest to właściwy sposób na zrobienie tego (chociaż .jest to jedyny meta-char wyrażenia regularnego prawidłowy w nazwie DNS, preg_quote()lepiej opisuje zamierzoną operację)
DaveRandom
1
Należy wyjaśnić, że nonezgodnie ze specyfikacją nie jest to semantycznie poprawna wartość nagłówka (a przynajmniej nie robi tego, co to sugeruje). Jako taki return null;może mieć większy sens dla tego oddziału, w takim przypadku nagłówek nie powinien być wysyłany do klienta, więc powinien zostać sprawdzony przez osobę dzwoniącą.
DaveRandom
preg_quote()cytuje znak *, więc str_replace()na przykład pozostawia osierocone „\”.
Christoffer Bubach
1
jest to przydatne, spędziłem czas na problemie CORS, dopóki nie zdałem sobie sprawy, że moja witryna ma „www” w ajax, ale nie w strukturze permalink - twoje rozwiązanie pomogło mi zrozumieć, gdzie był problem i rozwiązało to dla mnie.
Sol
3

Podczas ustawiania Access-Control-Allow-Originw .htaccess działały tylko następujące elementy:

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

Próbowałem kilka innych je słów kluczowych Header append, Header set, nikt nie pracował jak zasugerowano w wielu odpowiedziach na SO, choć nie mam pojęcia, czy te słowa są nieaktualne lub niepoprawne dla nginx .

Oto moje kompletne rozwiązanie:

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]
AamirR
źródło
2

Mieliśmy podobne problemy z Font Awesome w statycznej domenie „bez plików cookie” podczas czytania czcionek z „domeny plików cookie” (www.domain.tld) ​​i ten post był naszym bohaterem. Zobacz tutaj: jak mogę rozwiązać problem z nagłówkiem odpowiedzi „Brak odpowiedzi nagłówka odpowiedzi CORS”?

Dla typów kopiuj / wklej-r (i podaj kilka rekwizytów) poskładałem to razem ze wszystkich wkładów i dodałem na początku pliku .htaccess katalogu głównego witryny:

<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 Secure, Super Elegant. Uwielbiam to: nie musisz otwierać przepustowości serwerów dla typów złodziei zasobów / hot-link-er.

Rekwizyty do: @Noyo @DaveRandom @ pratap-koritala

(Próbowałem zostawić to jako komentarz do zaakceptowanej odpowiedzi, ale nie mogę tego jeszcze zrobić)

kanidrive
źródło
0

Wygląda na to, że oryginalna odpowiedź dotyczyła wersji Apache 2.4. Nie działało to dla mnie. Oto, co musiałem zmienić, aby działało w 2.4. Będzie to działać dla dowolnej głębokości poddomeny twojej firmy .

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"
Jack K.
źródło
0

Musiałem nieco zmodyfikować odpowiedź Larsa , gdy osierocony \trafił do wyrażenia regularnego, aby porównać tylko rzeczywistego hosta (nie zwracając uwagi na protokół lub port) i chciałem wesprzeć localhostdomenę oprócz mojej domeny produkcyjnej. Dlatego zmieniłem $allowedparametr na tablicę.

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

Zastosowanie w następujący sposób:

if (isset($_SERVER['HTTP_ORIGIN'])) {
    header("Access-Control-Allow-Origin: " . getCORSHeaderOrigin(array("*.myproduction.com", "localhost"), $_SERVER['HTTP_ORIGIN']));
}
Koziorożec
źródło
0

w moim przypadku za pomocą kątownika

w moim przechwytywaczu HTTP ustawiłem

with Credentials: true.

w nagłówku żądania

hosam hemaily
źródło