Access-Control-Allow-Origin通配符子域,端口和协议


Answers:


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"

就是这样。

那些除了其所有子域之外还希望在父域(例如mywebsite.com)上启用CORS的用户,可以简单地用以下内容替换第一行中的正则表达式:

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

注意:为了符合规范和正确的缓存行为,请始终Vary: Origin为启用了CORS的资源添加响应标头,即使对于非CORS请求和来自不允许来源的请求也是如此(请参阅示例示例)。


1
当访问者使用相同的字体在多个子域之间跳转时,我们几乎遇到了这种情况(不是Vary Origin),并且行为不当。字体和access-control-origin标头也已缓存。我对此做了一些更改:如果请求来自我们允许的域之一,则使用“ Access-Control-Allow-Origin *”。也许这是通过我们以前没有的“可变起源”解决的……现在也添加了它。
Erik Melkersson

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虚拟主机配置中?
格伦(Glen)

252

CORS规格全有或全无。它仅支持*null或者确切的协议+域+端口:http : //www.w3.org/TR/cors/#access-control-allow-origin-response-header

您的服务器将需要使用正则表达式验证原始标头,然后可以在Access-Control-Allow-Origin响应标头中回显原始值。


7
@Dexter“ null”可用于响应“ null”来源,例如,当从file://方案发出CORS请求时。
2014年

130
极短视的是,CORS规范不支持OP的确切用例。
aroth,2014年

6
@aroth:并非如此,该规范允许实现使用他们想要的任何匹配语法。对于不支持该用例的实现来说,这将是非常短视的。换句话说,您为服务器指定的不是ACAO值,后者只是协议详细信息。我的假设是,有一个安全方案需要回显的源或从中得到好处,但是幼稚的实现只需要说“ OK”即可。
tne 2014年

3
2015年更新:此答案不完整,可能会导致缓存问题,因此应视为有害。请参阅以下我的答案以获取正确的实现(适用于Apache)和说明:stackoverflow.com/a/27990162/357774。另外,正如@neoth所指出的那样,该规范实际上确实允许OP的确切用例:w3.org/TR/cors/#resource-implementation。正如这个答案所指出的,这取决于服务器来实现。如上面提到的答案所示,这可以分三行完成。
Noyo 2015年

19
@Noyo-我会澄清我的原始意思。从根本上来说,CORS规范并不严格要求所有实施CORS的服务器都为OP的确切用例提供自动的内置支持。让每个用户使用自定义PHP代码,重写规则或所拥有的知识来构建自己的垫片,这是造成碎片,错误和灾难的秘诀。服务器开发人员应该比这更好地了解;如果没有,则CORS规范应强制他们这样做。
aroth

59

编辑:使用@ Noyo的解决方案,而不是此解决方案。它在负载下更简单,更清晰,并且性能可能更高。

此处仅保留历史记录!


我做了一些处理这个问题的工作,并提出了与Apache一起使用的可重用的.htaccess(或httpd.conf)解决方案:

<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. regex分组对性能有影响,但这不是必需的。
  2. 无法匹配域,并且仅适用于子域。

例如:当用于http://somedomain.mywebsite.com/时,它不会为http://mywebsite.com发送CORS标头

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"

要启用您的站点,只需在上面的Apache配置中将站点替换为“ mywebsite.com”即可。

要允许多个站点:

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()是因为这样做是正确的方法(即使这.是DNS名称中唯一有效的正则表达式元字符,也能preg_quote()更好地描述预期的操作)
DaveRandom 2015年

1
应该说明的none是,根据规范,标头不是语义上有效的值(或至少不执行其所隐含的含义)。这样,return null;对于该分支可能更有意义,在这种情况下,不应将标头发送给客户端,因此调用方应检查该标头。
DaveRandom 2015年

preg_quote()将加*号,因此str_replace()例如留下一个孤立的“ \”。
Christoffer Bubach '16

1
这很有用,我花了一些时间在CORS问题上,直到我意识到我的站点在ajax中具有“ www”,但在永久链接结构中却没有-您的解决方案帮助我了解了问题所在并为我解决了该问题。
索尔

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 appendHeader set没有工作在这么多的答案建议,虽然我不知道,如果这些关键字已经过时或无效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

从“ cookie域”(www.domain.tld)读取字体时,我们在静态“不含cookie的”域上遇到了Font Awesome的类似问题,而本文是我们的英雄。请参阅此处:如何解决“缺少跨域资源共享(CORS)响应标题” webfont问题?

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

我不得不修改Lars的答案,因为一个孤儿\最终出现在正则表达式中,只比较实际的主机(不注意协议或端口),并且我想支持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.

在请求的标题中

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.