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

Access-Control-Allow-Origin wildcard subdomains, ports and protocols

我正在尝试为所有子域,端口和协议启用CORS。

例如,我希望能够从http://sub.mywebsite.com:8080/运行XHR请求到https://www.mywebsite.com/*

通常情况下,我想启用匹配(并限于)的来源请求:

//*.mywebsite.com:*/*


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

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


基于DaveRandom的回答,我也在玩,并发现了一个稍微简单的Apache解决方案,它产生相同的结果(Access-Control-Allow-Origin动态设置为当前特定协议+域+端口),而不使用任何重写规则:

1
2
3
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})?)$

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


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

原始答案仅限于此处用于历史目的!!

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<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变量设置为根域,如果它与您的域匹配,它将在Access-Control-Allow-Origin:响应头值中将Origin:请求标头值回送给客户端。

另请注意,您可以使用sub.mydomain.com作为ACCESS_CONTROL_ROOT,它会将原点限制为sub.mydomain.com*.sub.mydomain.com(即它不必是域根)。允许变化的元素(协议,端口)可以通过修改正则表达式的URI匹配部分来控制。


我正在回答这个问题,因为接受的答案与主域名不匹配,只适用于子域名。此外,正则表达式分组是性能损失,这是不必要的。

例如:在http://somedomain.mywebsite.com/工作时,它不会为http://mywebsite.com发送CORS标头

1
2
3
4
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"的位置即可。

允许多个站点:

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

测试部署后:

以下卷曲响应应在更改后具有"Access-Control-Allow-Origin"标头。

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


我需要一个只有PHP的解决方案,所以万一有人也需要它。它需要一个允许的输入字符串,如"* .example.com",如果输入匹配,则返回请求标头服务器名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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数据提供程序的测试用例:

1
2
3
4
5
6
7
8
9
10
11
//    <description>                                      <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'),


在.htaccess中设置Access-Control-Allow-Origin时,只有以下工作:

1
2
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,没有按照SO上的许多答案的建议工作,但我不知道这些关键字是否过时或对nginx无效。

这是我的完整解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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]


当从"cookie域"(www.domain.tld)读取字体时,我们在静态"无cookie"域上遇到与Font Awesome类似的问题,这篇文章是我们的英雄。请参阅此处:如何修复"缺少跨域资源共享(CORS)响应标头"webfont问题?

对于copy / paste-r类型(以及提供一些道具),我将所有贡献拼凑在一起,并将其添加到站点根目录的.htaccess文件的顶部:

1
2
3
4
5
6
7
<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

(我试着将此作为对已接受答案的评论,但我还不能这样做)


对于Spring Boot,我发现这个RegexCorsConfiguration扩展了官方的CorsConfiguration:https://github.com/looorent/spring-security-jwt/blob/master/src/main/java/be/looorent/security/jwt/RegexCorsConfiguration.java


我不得不修改Lars的答案,因为孤立的\最终在正则表达式中,只比较实际的主机(没有注意协议或端口)我想支持localhost域除了我的生产 域。 因此我将$allowed参数更改为数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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;
}

用法如下:

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

看起来原来的答案是针对Apache 2.4之前的。它对我不起作用。这是我必须改变以使其在2.4中工作的内容。这适用于yourcompany.com的任何深度子域。

1
2
3
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"

Lars回答略有变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?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;