Подстановочные поддомены Access-Control-Allow-Origin, порты и протоколы


312

Я пытаюсь включить CORS для всех поддоменов, портов и протокола.

Например, я хочу иметь возможность выполнить запрос XHR с http://sub.mywebsite.com:8080/ на https://www.mywebsite.com/ *

Как правило, я хотел бы включить запрос от сопоставления источников (и ограничен):

//*.mywebsite.com:*/*

Ответы:


207

На основании DaveRandom в ответе , я также играл вокруг и нашел несколько более простое решение Apache , который производит один и тот же результат ( Access-Control-Allow-Originустанавливаются на текущий конкретный протокол + домен + порт динамически) без использования каких - либо правил перезаписи:

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"

И это все.

Те, кто хочет включить CORS в родительском домене (например, mywebsite.com) в дополнение ко всем его поддоменам, могут просто заменить регулярное выражение в первой строке следующим:

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

Примечание. Для соответствия спецификациям и правильного поведения кэширования ВСЕГДА добавляйте Vary: Originзаголовок ответа для ресурсов с поддержкой CORS, даже для запросов не-CORS и запросов из недопустимого источника (см. Пример почему ).


1
У нас было почти это (не Vary Origin) и мы получили плохое поведение, когда посетители перепрыгивали между несколькими поддоменами, используя один и тот же шрифт. Шрифт и заголовок access-control-origin также были кэшированы. Я сделал небольшое изменение в этом: я использую «Access-Control-Allow-Origin *», если запрос от одного из наших разрешенных доменов. Возможно, это было решено с помощью "Vary Origin", которого у нас не было раньше ... теперь тоже добавил.
Эрик Мелкерссон

2
Не работает для основного домена «mywebsite.com»
biology.info

1
@pgmann, //в этом контексте нет необходимости избегать , поскольку в Apache conf не используются регулярные выражения, разделенные косой чертой. Regexr жалуется, потому что в этом контексте косые черты имеют особое значение в качестве разделителей.
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.
Аэро Ван

3
Где вы размещаете этот код? .htaccess или в конфиге виртуального хоста apache?
Глен

252

Спецификация CORS "все или ничего". Он поддерживает только *, nullили точный протокол + домен + порт: http://www.w3.org/TR/cors/#access-control-allow-origin-response-header

Вашему серверу потребуется проверить заголовок источника с помощью регулярного выражения, а затем вы можете отобразить значение источника в заголовке ответа Access-Control-Allow-Origin.


7
@Dexter "null" может использоваться в ответ на "нулевое" происхождение, например, при выполнении запроса CORS из схемы file: //.
Монсур

130
Глубоко недальновидно, что спецификация CORS не будет поддерживать точный вариант использования OP .
aroth

6
@aroth: Не совсем, спецификация позволяет реализациям использовать любой подходящий синтаксис; было бы только недальновидно для реализации не поддерживать этот вариант использования. Другими словами, то, что вы указываете своему серверу, не является значением ACAO, последнее - просто подробности протокола. Я предполагаю, что есть сценарий безопасности, который требует или извлекает выгоду из возврата источника, но наивная реализация работает, просто говоря «ОК» или нет.
TNE

3
Обновление от 2015 года: этот ответ следует считать вредным, поскольку он неполный и может привести к проблемам с кэшированием. Пожалуйста, смотрите мой ответ ниже для правильной реализации (для Apache) и объяснения: stackoverflow.com/a/27990162/357774 . Кроме того , @aroth как TNE указывает, спецификации на самом деле делает позволяют точно случай использования в OP: в w3.org/TR/cors/#resource-implementation . И, как указывает этот ответ, это зависит от сервера, чтобы реализовать это. Это можно сделать в 3 строки, как видно из ответа, указанного выше.
Noyo

19
@Noyo - тогда я уточню свой первоначальный смысл. Глубоко недальновидно, что для спецификации CORS не требуется, чтобы все серверы, реализующие CORS, обеспечивали автоматическую встроенную поддержку точного варианта использования OP . Предоставление каждому отдельному пользователю возможности создавать свои собственные прокладки с использованием пользовательского кода PHP, правил перезаписи или «что у вас есть» - это рецепт для фрагментации, ошибок и аварий. Разработчики сервера должны знать это лучше; и если они этого не делают, спецификация CORS должна заставить их.
aroth

59

РЕДАКТИРОВАТЬ : Используйте решение @ Noyo вместо этого. Это проще, понятнее и, вероятно, намного эффективнее под нагрузкой.

ОРИГИНАЛЬНЫЙ ОТВЕТ ОСТАЕТСЯ ТОЛЬКО ДЛЯ ИСТОРИЧЕСКИХ ЦЕЛЕЙ !!


Я немного поэкспериментировал с этой проблемой и придумал это повторно используемое решение .htaccess (или httpd.conf), которое работает с 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>

Просто установите ACCESS_CONTROL_ROOTпеременную в верхней части блока для вашего корневого домена, и она будет Origin:возвращать клиенту Access-Control-Allow-Origin:значение заголовка запроса в значении заголовка ответа, если оно соответствует вашему домену.

Также обратите внимание, что вы можете использовать sub.mydomain.comкак ACCESS_CONTROL_ROOTи он ограничит происхождение до sub.mydomain.comи *.sub.mydomain.com(то есть он не должен быть корневым доменом). Элементами, которым разрешено варьироваться (протокол, порт), можно управлять, изменяя URI-совпадающую часть регулярного выражения.


22

Я отвечаю на этот вопрос, потому что принятый ответ не может сделать следующее

  1. Группировка регулярных выражений - это снижение производительности , которое не является необходимым.
  2. не может соответствовать первичному домену, и это работает только для поддоменов.

Например: он не будет отправлять заголовки CORS для http://mywebsite.com, пока работает для 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"

Чтобы включить его для своего сайта, просто поместите свой сайт вместо «mywebsite.com» в приведенной выше конфигурации Apache.

Чтобы разрешить несколько сайтов:

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

Тестирование после развертывания:

Следующий ответ скручивания должен иметь заголовок «Access-Control-Allow-Origin» после изменения.

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

12

Мне нужно было решение только для PHP, так что на всякий случай оно тоже кому-то нужно. Он принимает допустимую строку ввода, например "* .example.com", и возвращает имя сервера заголовка запроса, если ввод совпадает.

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

А вот тестовые примеры для провайдера данных 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, отредактированный для использования, preg_quote()потому что это правильный способ сделать это (хотя .это единственный метасимвол regexp, допустимый в DNS-имени, он preg_quote()лучше описывает предполагаемую операцию)
DaveRandom

1
Следует уточнить, что noneне является семантически допустимым значением для заголовка (или, по крайней мере, не делает то, что оно подразумевает) согласно спецификации. Таким образом, это return null;может иметь больше смысла для этой ветви, и в этом случае заголовок не должен отправляться клиенту, поэтому вызывающий должен проверить его.
DaveRandom

preg_quote()будет заключать в кавычки знак *, и поэтому, str_replace()например, оставляет сиротами "\".
Христоффер Бубах

1
это полезно, я потратил время на проблему с CORS, пока не понял, что на моем сайте есть «www» в ajax, но не в структуре постоянных ссылок - ваше решение помогло мне понять, где была проблема, и решило ее для меня.
Соль

3

При установке Access-Control-Allow-Originв .htaccess работало только следующее:

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

Я попробовал несколько других предлагаемых ключевых слов Header append, Header set, никто не работал , как это предлагается во многих ответах на SO, хотя я понятия не имею , если эти ключевые слова устарели или не действительны для Nginx .

Вот мое полное решение:

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

У нас были похожие проблемы с Font Awesome в статическом домене без файлов cookie при чтении шрифтов из домена cookie (www.domain.tld), и этот пост стал нашим героем. См. Здесь: Как устранить проблему с веб-шрифтом «Отсутствует заголовок ответа общего ресурса (CORS)»?

Для типов copy / paste-r (и для некоторых реквизитов) я собрал это воедино из всех вкладов и добавил в начало файла .htaccess корня сайта:

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

Супер Безопасный, Супер Элегантный. Вам это нравится: вам не нужно открывать пропускную способность ваших серверов для типов воров / горячих ссылок.

Реквизит: @Noyo @DaveRandom @ pratap-koritala

(Я пытался оставить это как комментарий к принятому ответу, но пока не могу этого сделать)



0

Похоже, что оригинальный ответ был для предварительно Apache 2.4. Это не сработало для меня. Вот что я должен был изменить, чтобы он работал в 2.4. Это будет работать для любой глубины субдомена 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

Мне пришлось немного изменить ответ Ларса , поскольку осиротевший \оказался в регулярном выражении, чтобы сравнить только фактический хост (не обращая внимания на протокол или порт), и я хотел поддерживать localhostдомен помимо моего рабочего домена. Таким образом, я изменил $allowedпараметр на массив.

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

Использование следующим образом:

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

0

в моем случае используется угловая

в моем HTTP-перехватчике я установил

with Credentials: true.

в шапке запроса

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.