Subdomínios, portas e protocolos curinga de acesso-controle-permissão-origem

312

Estou tentando habilitar o CORS para todos os subdomínios, portas e protocolos.

Por exemplo, desejo poder executar uma solicitação XHR de http://sub.mywebsite.com:8080/ para https://www.mywebsite.com/ *

Normalmente, gostaria de ativar a solicitação de origens correspondentes (e limitadas a):

//*.mywebsite.com:*/*

Elie
fonte

Respostas:

207

Com base na resposta de DaveRandom , eu também estava brincando e encontrei uma solução Apache um pouco mais simples que produz o mesmo resultado ( Access-Control-Allow-Originé definido com o protocolo específico atual + domínio + porta dinamicamente) sem usar nenhuma regra de reescrita:

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 é isso.

Aqueles que desejam ativar o CORS no domínio pai (por exemplo, mywebsite.com), além de todos os seus subdomínios, podem simplesmente substituir a expressão regular na primeira linha por esta:

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

Nota: Para conformidade com especificações e comportamento correto de armazenamento em cache, SEMPRE inclua o Vary: Origincabeçalho de resposta para recursos habilitados para CORS, mesmo para solicitações não-CORS e de origem não permitida (veja o exemplo por que ).

Noyo
fonte
1
Tivemos quase isso (não a Vary Origin) e tivemos um comportamento ruim quando os visitantes saltaram entre vários subdomínios usando a mesma fonte. A fonte e o cabeçalho de origem do controle de acesso também foram armazenados em cache. Fiz uma pequena alteração neste: uso "Access-Control-Allow-Origin *" se a solicitação for de um de nossos domínios permitidos. Talvez isso tenha sido resolvido com o "Vary Origin" que não tínhamos antes ... agora acrescentou isso também.
Erik Melkersson
2
Não funciona para o principal domínio 'mywebsite.com'
biology.info
1
@pgmann, não há necessidade de escapar //desse contexto, já que o Apache conf não usa expressões regulares delimitadas por barras. O Regexr reclama porque, nesse contexto, as barras têm um significado especial como delimitadores.
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
Onde você coloca esse código? .htaccess ou na configuração do host virtual do apache?
Glen
252

A especificação do CORS é tudo ou nada. Ele suporta apenas *, nullou o protocolo exato + domínio + porta: http://www.w3.org/TR/cors/#access-control-allow-origin-response-header

Seu servidor precisará validar o cabeçalho de origem usando a regex e, em seguida, você poderá ecoar o valor de origem no cabeçalho de resposta Access-Control-Allow-Origin.

Monsur
fonte
7
@Dexter "null" pode ser usado em resposta a uma origem "null", por exemplo, ao fazer uma solicitação CORS a partir de um esquema file: //.
Monsur
130
É profundamente míope que as especificações do CORS não suportem o caso de uso exato do OP .
Aroth # 8/14
6
@aroth: Na verdade, a especificação permite que as implementações usem qualquer sintaxe correspondente que desejarem; seria apenas uma visão curta para uma implementação que não suporta esse caso de uso. Em outras palavras, o que você especifica para o servidor não é o valor ACAO; este último é apenas um detalhe do protocolo. Minha suposição é que existe um cenário de segurança que requer ou se beneficia com a eco da origem, mas uma implementação ingênua funciona apenas dizendo "OK" ou não.
tne
3
Atualização de 2015: esta resposta deve ser considerada prejudicial, pois está incompleta e pode levar a problemas de cache. Por favor, veja minha resposta abaixo para uma implementação adequada (para Apache) e explicação: stackoverflow.com/a/27990162/357774 . Além disso, @aroth, como tne assinala, a especificação realmente faz permitir caso de uso exato de OP: w3.org/TR/cors/#resource-implementation . E como esta resposta aponta, cabe ao servidor implementá-la. Isso pode ser feito em 3 linhas, como visto na resposta mencionada acima.
Noyo
19
@ Nyo - eu vou esclarecer o meu significado original então. É profundamente míope que a especificação do CORS não exija estritamente todos os servidores que implementam o CORS para fornecer suporte interno automático para o caso de uso exato do OP . Deixando para cada usuário individual criar seu próprio calço usando código PHP personalizado, reescrever regras ou o que você tem, é uma receita para fragmentação, bugs e desastres. Os desenvolvedores de servidores devem saber melhor que isso; e se não o fizerem, a especificação do CORS deve forçá-los a fazê-lo.
Aroth 27/08/2015
59

EDIT : Use a solução da @ Noyo em vez desta. É mais simples, mais claro e provavelmente muito mais eficiente sob carga.

RESPOSTA ORIGINAL DEIXADA AQUI PARA FINS HISTÓRICOS SOMENTE !!


Eu brinquei com esse problema e criei esta solução reutilizável .htaccess (ou httpd.conf) que funciona com o 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 definir a ACCESS_CONTROL_ROOTvariável na parte superior do bloco para o seu domínio raiz e ele ecoará o Origin:valor do cabeçalho da solicitação de volta ao cliente noAccess-Control-Allow-Origin: valor do cabeçalho de resposta, se ele corresponder ao seu domínio.

Note também que você pode usar sub.mydomain.comcomo o ACCESS_CONTROL_ROOTe ele vai limitar origens sub.mydomain.come *.sub.mydomain.com(ou seja, ele não tem de ser a raiz do domínio). Os elementos que podem variar (protocolo, porta) podem ser controlados modificando a parte correspondente do URI do regex.

DaveRandom
fonte
22

Estou respondendo a essa pergunta, porque a resposta aceita não pode seguir

  1. agrupamento regex é um sucesso , o que não é necessário.
  2. não pode corresponder ao domínio principal e funciona apenas para o subdomínio.

Por exemplo: ele não envia cabeçalhos CORS para http://mywebsite.com enquanto trabalha para 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"

Para ativar o seu site, basta colocá-lo no lugar de "mywebsite.com" na Configuração Apache acima.

Para permitir vários sites:

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

Teste Após a implantação:

A resposta de ondulação a seguir deve ter o cabeçalho "Access-Control-Allow-Origin" após a alteração.

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

Eu precisava de uma solução apenas para PHP, portanto, caso alguém precise dela também. Ele pega uma sequência de entrada permitida como "* .example.com" e retorna o nome do servidor do cabeçalho da solicitação, se a entrada corresponder.

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

E aqui estão os casos de teste para um provedor de dados 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
fonte
1
+1, editado para uso preg_quote()porque essa é a maneira correta de fazê-lo (mesmo que .é a única regexp meta caractere válido em um nome de DNS, preg_quote()descreve a operação destina-se melhor)
DaveRandom
1
Deve-se esclarecer que nonenão é um valor semanticamente válido para o cabeçalho (ou pelo menos não faz o que implica) de acordo com a especificação. Como tal, return null;pode fazer mais sentido para essa ramificação e, nesse caso, nenhum cabeçalho deve ser enviado ao cliente; portanto, deve ser verificado pelo chamador.
DaveRandom
preg_quote()citará o sinal * e, str_replace()por exemplo, deixa um "\" órfão.
Christoffer Bubach
1
isso é útil, passei um tempo na questão do CORS até perceber que meu site tinha "www" no ajax, mas não na estrutura do permalink - sua solução me ajudou a entender onde estava o problema e resolveu isso para mim.
Sol
3

Ao definir Access-Control-Allow-Originem .htaccess, apenas o seguinte funcionou:

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

Tentei várias outras palavras-chave sugeridas Header append, Header set, nenhum funcionou como sugerido em muitas respostas sobre SO, embora eu não tenho idéia se essas palavras-chave são desatualizados ou não válidos para nginx .

Aqui está minha solução 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]
AamirR
fonte
2

Estávamos tendo problemas semelhantes com o Font Awesome em um domínio estático "sem cookies" ao ler fontes do "domínio de cookies" (www.domain.tld) ​​e essa postagem foi nosso herói. Consulte aqui: Como corrigir o problema de fonte da web 'Faltando cabeçalho de resposta de compartilhamento de recursos de origem cruzada (CORS)'?

Para os tipos copy / paste-r (e para dar algumas sugestões), juntei tudo isso de todas as contribuições e o adicionei ao topo do arquivo .htaccess da raiz do 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 seguro, super elegante. Adorei: você não precisa abrir a largura de banda dos servidores para roubar recursos / tipos de hot-link-er.

Adereços para: @Noyo @DaveRandom @ pratap-koritala

(Tentei deixar isso como um comentário para a resposta aceita, mas ainda não posso fazer isso)

Kanidrive
fonte
0

Parece que a resposta original foi anterior ao Apache 2.4. Não funcionou para mim. Aqui está o que eu tive que mudar para fazê-lo funcionar na 2.4. Isso funcionará para qualquer profundidade de subdomínio da suaempresa.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"
Jack K
fonte
0

Eu tive que modificar um pouco a resposta de Lars , como um órfão \acabou na regex, para comparar apenas o host real (sem prestar atenção ao protocolo ou porta) e queria oferecer suporte ao localhostdomínio além do meu domínio de produção. Assim, mudei o $allowedparâmetro para uma matriz.

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

Uso da seguinte maneira:

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

no meu caso usando angular

no meu interceptor HTTP, defino

with Credentials: true.

no cabeçalho da solicitação

hosam hemaily
fonte