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

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

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

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

//*.mywebsite.com:*/*

16 ответов

Решение

Основываясь на ответе 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 и запросов из недопустимого источника (см. пример почему).

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

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

РЕДАКТИРОВАТЬ: Используйте решение @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-совпадающую часть регулярного выражения.

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

Например: он не будет отправлять заголовки 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

Мне нужно было только 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'),

При настройке 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]

Для Spring Boot я нашел это RegexCorsConfiguration который расширяет официальный CorsConfiguration: https://github.com/looorent/spring-security-jwt/blob/master/src/main/java/be/looorent/security/jwt/RegexCorsConfiguration.java

У нас были похожие проблемы с 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

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

Похоже, что оригинальный ответ был для предварительно 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"

Для весны вы должны использоватьallowedOriginPatterns, он делает именно то, что вы хотите

Альтернатива setAllowedOrigins(java.util.List<java.lang.String>), которая поддерживает более гибкие шаблоны источников с "*" в любом месте имени хоста в дополнение к спискам портов. Примеры:

      https://*.domain1.com -- domains ending with domain1.com
https://*.domain1.com:[8080,8081] -- domains ending with domain1.com on port 8080 or port 8081
https://*.domain1.com:[*] -- domains ending with domain1.com on any port, including the default port 

В отличие от allowOrigins, который поддерживает только " " и не может использоваться с allowCredentials, при совпадении с allowOriginPattern заголовок ответа Access-Control-Allow-Origin устанавливается на совпадающее происхождение, а не на " " или на шаблон. Следовательно, allowOriginPatterns можно использовать в сочетании с setAllowCredentials(java.lang.Boolean), для которого задано значение true.

Например:

      @Bean
public WebMvcConfigurer corsConfigurer() {
    return new WebMvcConfigurer() {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            List<String> allowedOrigins = List.of("https://*.example.com", "http://*.example2.com[80,8080]");
            registry.addMapping("/**").allowedOriginPatterns(allowedOrigins.toArray(new String[0]));

        }
    };
}

У меня возникли проблемы с принятым ответом. Незначительная настройка с использованиемHeader always setдопускается постоянное поведение. Обратите внимание, что приведенный ниже пример фактически является подстановочным решением и может привести к непредвиденному поведению, например, к тому, что пользователи смогут получить доступ к вашему (потенциально) сайту с поддержкой SSL из своих локально размещенных окружений. Я бы НЕ советовал такое поведение для производственных веб-серверов, но варианты использования у всех разные, поэтому, учитывая отказ от ответственности, я считаю, что это полезно для непроизводственных веб-серверов. Вы можете использовать другие примеры для получения более индивидуальных инструкций.

      # https://stackoverflow.com/questions/14003332/access-control-allow-origin-wildcard-subdomains-ports-and-protocols/27990162#27990162
SetEnvIf Origin ^(https?://.*(?::\d{1,5})?)$ CORS_ALLOW_ORIGIN=$1
Header always set Access-Control-Allow-Origin %{CORS_ALLOW_ORIGIN}e env=CORS_ALLOW_ORIGIN
Header merge Vary "Origin"
# todo - Cached for a day - 86400 
Header always set Access-Control-Max-Age: 0
Header always set Access-Control-Allow-Methods "GET, POST, PATCH, PUT, DELETE, OPTIONS"
Header always set Access-Control-Allow-Headers: *

Мне нужен вариант с несколькими доменами, и это решение, которое я использую, с python а также flask,

      VALID_DOMAINS = 'https://subdomain1.example.com', 'https://subdomain2.example.com'


def handle_request(request):
    origin = request.headers.get('Origin')

    if request.method == 'OPTIONS':
        if origin not in :
            return ''

        headers = {
            'Access-Control-Allow-Origin': origin,
            'Access-Control-Allow-Methods': 'GET',
            'Access-Control-Allow-Headers': 'Content-Type',
            'Access-Control-Max-Age': '3600',
        }
        return '', 204, headers

    return (
        main_function_with_logic(request),
        200,
        {'Access-Control-Allow-Origin': origin,
         ...}
    )

Очевидно, вы можете расширить VALID_DOMAINS до любого размера и того, что хотите (не https, другой порт и т. Д.), И просто проверить это в запросе.

Я предпочитаю это решение, чем решение с подстановочными знаками, поэтому это мой выбор на серверах, которые я использую.

Мне пришлось немного изменить ответ Ларса, как сироту \ в конечном итоге в регулярном выражении, чтобы сравнить только фактический хост (не обращая внимания на протокол или порт), и я хотел поддержать 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']));
}

В моем случае с использованием углового

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

with Credentials: true.

в заголовке запроса

С небольшим изменением ответа Ларса.

<?php
function validateOrigin($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;
}

// CORS Preflight
if($_SERVER['REQUEST_METHOD'] === 'OPTIONS')
{
    header('Access-Control-Allow-Origin: '.validateOrigin('https://*.domain.com', $_SERVER['HTTP_ORIGIN']));
    header('X-Response-Code: 204', true, 204);
    header('Access-Control-Allow-Methods: GET');
    header('Access-Control-Allow-Headers: Content-Type');
} elseif($_SERVER['REQUEST_METHOD'] === 'GET'
    && isset($_GET['VARIABLEHERE'])
) {
    // CODE HERE

    if($info["http_code"] === 200){
        // 200 OK
        header('Content-type: application/json; charset=utf-8');
        // CORS headers
        header('Access-Control-Allow-Origin: '.validateOrigin('https://*.domain.com', $_SERVER['HTTP_ORIGIN']));
        echo $response;
    } else {
        header('X-Service-Response-Code: '.$info["http_code"], true, $info["http_code"]);
    }
} else {
    // 400 bad request.
    header('X-Response-Code: 400', true, 400);
}

exit;

Еще проще вы можете использовать аннотацию @CrossOrigin с атрибутом originPatterns, установленным на то, что вам нужно, она поддерживает подстановочные знаки и работает с разрешенными учетными данными, для которых установлено значение true.

Другие вопросы по тегам