Access-Control-Allow-Origin Домены множественного происхождения?

Есть ли способ разрешить несколько кросс-доменов, используя Access-Control-Allow-Origin заголовок?

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

Как пример, что-то вроде этого:

Access-Control-Allow-Origin: http://domain1.example, http://domain2.example

Я пробовал приведенный выше код, но он не работает в Firefox.

Можно ли указать несколько доменов или я застрял только на одном?

37 ответов

Решение

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

С .htaccess Вы можете сделать это так:

# ----------------------------------------------------------------------
# Allow loading of external fonts
# ----------------------------------------------------------------------
<FilesMatch "\.(ttf|otf|eot|woff)$">
    <IfModule mod_headers.c>
        SetEnvIf Origin "http(s)?://(www\.)?(google.com|staging.google.com|development.google.com|otherdomain.example|dev02.otherdomain.example)$" AccessControlAllowOrigin=$0
        Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
        Header merge Vary Origin
    </IfModule>
</FilesMatch>

Другое решение, которое я использую в PHP:

$http_origin = $_SERVER['HTTP_ORIGIN'];

if ($http_origin == "http://www.domain1.com" || $http_origin == "http://www.domain2.com" || $http_origin == "http://www.domain3.com")
{  
    header("Access-Control-Allow-Origin: $http_origin");
}

Это сработало для меня:

SetEnvIf Origin "^http(s)?://(.+\.)?(domain\.example|domain2\.example)$" origin_is=$0 
Header always set Access-Control-Allow-Origin %{origin_is}e env=origin_is

Когда положить в .htaccess, это будет работать наверняка.

У меня была такая же проблема с woff-шрифтами, доступ к нескольким поддоменам должен был быть. Чтобы разрешить субдомены, я добавил что-то вроде этого в мой httpd.conf:

SetEnvIf Origin "^(.*\.example\.com)$" ORIGIN_SUB_DOMAIN=$1
<FilesMatch "\.woff$">
    Header set Access-Control-Allow-Origin "%{ORIGIN_SUB_DOMAIN}e" env=ORIGIN_SUB_DOMAIN
</FilesMatch>

Для нескольких доменов вы можете просто изменить регулярное выражение в SetEnvIf,

Вот как вернуть заголовок Origin, если он соответствует вашему домену с Nginx, это полезно, если вы хотите обслуживать шрифт несколькими поддоменами:

location /fonts {
    # this will echo back the origin header
    if ($http_origin ~ "example.org$") {
        add_header "Access-Control-Allow-Origin" $http_origin;
    }
}

Для приложений ExpressJS вы можете использовать:

app.use(function(req, res, next) {
    const corsWhitelist = [
        'https://domain1.example',
        'https://domain2.example',
        'https://domain3.example'
    ];
    if (corsWhitelist.indexOf(req.headers.origin) !== -1) {
        res.header('Access-Control-Allow-Origin', req.headers.origin);
        res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
    }

    next();
});

Вот что я сделал для приложения PHP, которое запрашивает AJAX

$request_headers        = apache_request_headers();
$http_origin            = $request_headers['Origin'];
$allowed_http_origins   = array(
                            "http://myDumbDomain.example"   ,
                            "http://anotherDumbDomain.example"  ,
                            "http://localhost"  ,
                          );
if (in_array($http_origin, $allowed_http_origins)){  
    @header("Access-Control-Allow-Origin: " . $http_origin);
}

Если запрашивающий источник разрешен моим сервером, верните $http_origin само по себе как ценность Access-Control-Allow-Origin заголовок вместо возврата * подстановочные.

Как уже упоминалось выше, Access-Control-Allow-Origin должен быть уникальным и Vary должен быть установлен в Origin если вы находитесь за CDN (Сеть доставки контента).

Соответствующая часть моей конфигурации Nginx:

if ($http_origin ~* (https?://.*\.mydomain.example(:[0-9]+)?)) {
  set $cors "true";
}
if ($cors = "true") {
  add_header 'Access-Control-Allow-Origin' "$http_origin";
  add_header 'X-Frame-Options' "ALLOW FROM $http_origin";
  add_header 'Access-Control-Allow-Credentials' 'true';
  add_header 'Vary' 'Origin';
}

Для пользователей Nginx разрешить CORS для нескольких доменов. Мне нравится пример @marshall, хотя его ответы соответствуют только одному домену. Чтобы сопоставить список доменов и поддоменов, это регулярное выражение облегчает работу со шрифтами:

location ~* \.(?:ttf|ttc|otf|eot|woff|woff2)$ {
   if ( $http_origin ~* (https?://(.+\.)?(domain1|domain2|domain3)\.(?:me|co|com)$) ) {
      add_header "Access-Control-Allow-Origin" "$http_origin";
   }
}

Это будет отображать только заголовки "Access-Control-Allow-Origin", которые соответствуют данному списку доменов.

Есть один недостаток, о котором вы должны знать: как только вы отправляете исходные файлы на CDN (или любой другой сервер, на котором не разрешены сценарии), или если ваши файлы кэшируются на прокси-сервере, изменение ответа основывается на "Origin" заголовок запроса не будет работать.

Для нескольких доменов, в вашем .htaccess:

<IfModule mod_headers.c>
    SetEnvIf Origin "http(s)?://(www\.)?(domain1.example|domain2.example)$" AccessControlAllowOrigin=$0$1
    Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
    Header set Access-Control-Allow-Credentials true
</IfModule>

Вот решение для веб-приложения Java, основанное на ответе yesthatguy.

Я использую Джерси REST 1.x

Сконфигурируйте web.xml, чтобы знать о REST Джерси и CORSResponseFilter

 <!-- Jersey REST config -->
  <servlet>    
    <servlet-name>JAX-RS Servlet</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <init-param> 
        <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
        <param-value>true</param-value>
    </init-param>
    <init-param>
      <param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
      <param-value>com.your.package.CORSResponseFilter</param-value>
    </init-param>   
    <init-param>
        <param-name>com.sun.jersey.config.property.packages</param-name>
        <param-value>com.your.package</param-value>
    </init-param>        
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>JAX-RS Servlet</servlet-name>
    <url-pattern>/ws/*</url-pattern>
  </servlet-mapping>

Вот код для CORSResponseFilter

import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerResponse;
import com.sun.jersey.spi.container.ContainerResponseFilter;


public class CORSResponseFilter implements ContainerResponseFilter{

@Override
public ContainerResponse filter(ContainerRequest request,
        ContainerResponse response) {

    String[] allowDomain = {"http://localhost:9000","https://my.domain.example"};
    Set<String> allowedOrigins = new HashSet<String>(Arrays.asList (allowDomain));                  

    String originHeader = request.getHeaderValue("Origin");

    if(allowedOrigins.contains(originHeader)) {
        response.getHttpHeaders().add("Access-Control-Allow-Origin", originHeader);

        response.getHttpHeaders().add("Access-Control-Allow-Headers",
                "origin, content-type, accept, authorization");
        response.getHttpHeaders().add("Access-Control-Allow-Credentials", "true");
        response.getHttpHeaders().add("Access-Control-Allow-Methods",
                "GET, POST, PUT, DELETE, OPTIONS, HEAD");
    }

    return response;
}

}

Для IIS 7.5+ с установленным модулем URL Rewrite 2.0 см. Этот ответ SO

Может я и не прав, но насколько я вижу Access-Control-Allow-Origin имеет "origin-list" в качестве параметра.

По определению origin-list является:

origin            = "origin" ":" 1*WSP [ "null" / origin-list ]
origin-list       = serialized-origin *( 1*WSP serialized-origin )
serialized-origin = scheme "://" host [ ":" port ]
                  ; <scheme>, <host>, <port> productions from RFC3986

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

Код PHP:

$httpOrigin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : null;
if (in_array($httpOrigin, [
    'http://localhost:9000', // Co-worker dev-server
    'http://127.0.0.1:9001', // My dev-server
])) header("Access-Control-Allow-Origin: ${httpOrigin}");
header('Access-Control-Allow-Credentials: true');

Я изо всех сил пытался настроить это для домена, работающего по протоколу HTTPS, поэтому я решил поделиться этим решением. Я использовал следующую директиву в моем файле httpd.conf:

    <FilesMatch "\.(ttf|otf|eot|woff)$">
            SetEnvIf Origin "^http(s)?://(.+\.)?example\.com$" AccessControlAllowOrigin=$0
            Header set Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
    </FilesMatch>

+ Изменить example.com на ваше доменное имя. Добавьте это внутри <VirtualHost x.x.x.x:xx> в вашем файле httpd.conf. Обратите внимание, что если ваш VirtualHost имеет суффикс порта (например, :80) тогда эта директива не будет применяться к HTTPS, поэтому вам нужно будет также перейти в /etc/apache2/sites-available/default-ssl и добавить ту же директиву в этот файл внутри <VirtualHost _default_:443> раздел.

После того, как файлы конфигурации обновлены, вам нужно будет выполнить следующие команды в терминале:

a2enmod headers
sudo service apache2 reload

Если у вас проблемы со шрифтами, используйте:

<FilesMatch "\.(ttf|ttc|otf|eot|woff)$">
    <IfModule mod_headers>
        Header set Access-Control-Allow-Origin "*"
    </IfModule>
</FilesMatch>

Для довольно простого копирования / вставки для приложений.NET я написал это, чтобы включить CORS из файла global.asax. Этот код следует советам, данным в принятом в настоящее время ответе, отражая происхождение, указанное в запросе в ответе. Это эффективно достигает "*" без его использования. Причина этого заключается в том, что он включает несколько других функций CORS, включая возможность отправки AJAX XMLHttpRequest с атрибутом withCredentials, установленным в значение true.

void Application_BeginRequest(object sender, EventArgs e)
{
    if (Request.HttpMethod == "OPTIONS")
    {
        Response.AddHeader("Access-Control-Allow-Methods", "GET, POST");
        Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
        Response.AddHeader("Access-Control-Max-Age", "1728000");
        Response.End();
    }
    else
    {
        Response.AddHeader("Access-Control-Allow-Credentials", "true");

        if (Request.Headers["Origin"] != null)
            Response.AddHeader("Access-Control-Allow-Origin" , Request.Headers["Origin"]);
        else
            Response.AddHeader("Access-Control-Allow-Origin" , "*");
    }
}

Более гибкий подход заключается в использовании выражений Apache 2.4. Вы можете сопоставить домены, пути и практически все остальные переменные запроса. Хотя ответ * для всех единственными, кто запросил этот ответ, являются те, которые в любом случае отвечают требованиям.

<IfModule mod_headers.c>
    <If "%{HTTP:Host} =~ /\\byourdomain\\.example$/i">
        Header set Access-Control-Allow-Origin "*"
    </If>
</IfModule>

Вот расширенный вариант для Apache, который включает в себя некоторые из последних и запланированных определений шрифтов:

<FilesMatch "\.(ttf|otf|eot|woff|woff2|sfnt|svg)$">
    <IfModule mod_headers.c>
        SetEnvIf Origin "^http(s)?://(.+\.)?(domainname1|domainname2|domainname3)\.(?:com|net|org)$" AccessControlAllowOrigin=$0$1$2
        Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
        Header set Access-Control-Allow-Credentials true
    </IfModule>
</FilesMatch>

AWS Lambda/API-шлюз

Для получения информации о том, как настроить несколько источников на бессерверном AWS Lambda и API Gateway (хотя это довольно крупное решение для чего-то, что, на первый взгляд, должно быть довольно простым), см. Здесь:

/questions/31123846/aws-api-gateway-cors-access-control-allow-origin-neskolko-zapisej/31123855#31123855


В настоящее время невозможно настроить несколько источников в API Gateway, см. Здесь: https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors-console.html), но рекомендация (в ответ выше):

  • проверить заголовок Origin, отправленный браузером
  • сверьте это с белым списком происхождения
  • если он совпадает, вернуть входящий источник как заголовок Access-Control-Allow-Origin, иначе вернуть заполнитель (источник по умолчанию).

Простое решение, очевидно, включает ALL (*) следующим образом:

exports.handler = async (event) => {
    const response = {
        statusCode: 200,
        headers: {
            "Access-Control-Allow-Origin": "*",
            "Access-Control-Allow-Credentials" : true // Required for cookies, authorization headers with HTTPS
        },
        body: JSON.stringify([{

Но может быть лучше сделать это на стороне шлюза API (см. Вторую ссылку выше).

HTTP_ORIGIN используется не всеми браузерами. Насколько безопасен HTTP_ORIGIN? Для меня это выглядит пустым в FF.
У меня есть сайты, которым я разрешаю доступ к своему сайту, отправляю через идентификатор сайта, затем я проверяю свою базу данных на запись с этим идентификатором и получаю значение столбца SITE_URL (www.yoursite.com).

header('Access-Control-Allow-Origin: http://'.$row['SITE_URL']);

Даже если отправка через действительный идентификатор сайта, запрос должен быть от домена, указанного в моей БД, связанной с этим идентификатором сайта.

Чтобы облегчить многодоменный доступ для службы ASMX, я создал эту функцию в файле global.asax:

protected void Application_BeginRequest(object sender, EventArgs e)
{
    string CORSServices = "/account.asmx|/account2.asmx";
    if (CORSServices.IndexOf(HttpContext.Current.Request.Url.AbsolutePath) > -1)
    {
        string allowedDomains = "http://xxx.yyy.example|http://aaa.bbb.example";

        if(allowedDomains.IndexOf(HttpContext.Current.Request.Headers["Origin"]) > -1)
            HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", HttpContext.Current.Request.Headers["Origin"]);

        if(HttpContext.Current.Request.HttpMethod == "OPTIONS")
            HttpContext.Current.Response.End();
    }
}

Это позволяет CORS обрабатывать OPTIONS глагол также.

У меня есть /questions/31127757/access-control-allow-origin-domenyi-mnozhestvennogo-proishozhdeniya/31127768#31127768 этот код работал хорошо, но выдает ошибку, когда пользователь входит на эту страницу. Я исправил эту проблему с помощью этого кода.

      if (isset($_SERVER['HTTP_ORIGIN'])) {
   $http_origin = $_SERVER['HTTP_ORIGIN'];
   if ($http_origin == "http://localhost:3000" || $http_origin == "http://api.loc/"){  
      header("Access-Control-Allow-Origin: $http_origin");
   }
}

Пример кода PHP для сопоставления поддоменов.

if( preg_match("/http:\/\/(.*?)\.yourdomain.example/", $_SERVER['HTTP_ORIGIN'], $matches )) {
        $theMatch = $matches[0];
        header('Access-Control-Allow-Origin: ' . $theMatch);
}

Для заголовка Access-Control-Allow-Origin можно указать только одно происхождение. Но вы можете указать источник в своем ответе в соответствии с запросом. Также не забудьте установить заголовок Vary. В PHP я бы сделал следующее:

    /**
     * Enable CORS for the passed origins.
     * Adds the Access-Control-Allow-Origin header to the response with the origin that matched the one in the request.
     * @param array $origins
     * @return string|null returns the matched origin or null
     */
    function allowOrigins($origins)
    {
        $val = $_SERVER['HTTP_ORIGIN'] ?? null;
        if (in_array($val, $origins, true)) {
            header('Access-Control-Allow-Origin: '.$val);
            header('Vary: Origin');

            return $val;
        }

        return null;
    }

  if (allowOrigins(['http://localhost', 'https://localhost'])) {
      echo your response here, e.g. token
  }

Ответ службы поддержки Google на показ объявлений через SSL и грамматику в самом RFC, похоже, указывает на то, что вы можете разделять пробелы URL-адресами. Не уверен, насколько хорошо это поддерживается в разных браузерах.

И еще один ответ в Джанго. Чтобы иметь одно представление, разрешающее CORS из нескольких доменов, вот мой код:

def my_view(request):
    if 'HTTP_ORIGIN' in request.META.keys() and request.META['HTTP_ORIGIN'] in ['http://allowed-unsecure-domain.com', 'https://allowed-secure-domain.com', ...]:
        response = my_view_response() # Create your desired response data: JsonResponse, HttpResponse...
        # Then add CORS headers for access from delivery
        response["Access-Control-Allow-Origin"] = request.META['HTTP_ORIGIN']
        response["Access-Control-Allow-Methods"] = "GET" # "GET, POST, PUT, DELETE, OPTIONS, HEAD"
        response["Access-Control-Max-Age"] = "1000"  
        response["Access-Control-Allow-Headers"] = "*"  
        return response

Если вы попробуете так много примеров кода, как я, чтобы заставить его работать с использованием CORS, стоит упомянуть, что вы должны сначала очистить кеш, чтобы попытаться, если он действительно работает, аналогично проблемам, когда старые изображения все еще присутствуют, даже если это удален на сервере (потому что он все еще сохраняется в вашем кэше).

Например, CTRL + SHIFT + DEL в Google Chrome, чтобы удалить кеш.

Это помогло мне использовать этот код после попытки многих чистых .htaccess Решения и это казалось единственным работающим (по крайней мере для меня):

    Header add Access-Control-Allow-Origin "http://google.com"
    Header add Access-Control-Allow-Headers "authorization, origin, user-token, x-requested-with, content-type"
    Header add Access-Control-Allow-Methods "PUT, GET, POST, DELETE, OPTIONS"

    <FilesMatch "\.(ttf|otf|eot|woff)$">
        <IfModule mod_headers.c>
            SetEnvIf Origin "http(s)?://(www\.)?(google.com|staging.google.com|development.google.com|otherdomain.com|dev02.otherdomain.net)$" AccessControlAllowOrigin=$0
            Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
        </IfModule>
    </FilesMatch>

Также обратите внимание, что широко распространено, что многие решения говорят, что вы должны ввести Header set ... но это Header add ..., Надеюсь, что это поможет кому-то, кто испытывает те же проблемы в течение нескольких часов, как я.

Ниже ответ специфичен для C#, но концепция должна быть применима ко всем различным платформам.

Чтобы разрешить перекрестные исходные запросы от веб-API, необходимо разрешить запросы Option для своего приложения и добавить примечание ниже на уровне контроллера.

[EnableCors (UrlString, Header, Method)] Теперь происхождение можно передавать только в виде строки. Поэтому, если вы хотите передать более одного URL-адреса в запросе, передайте его как разделенное запятыми значение.

UrlString = " https://a.hello.com,https://b.hello.com"

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