访问控制-允许起源通配符子域,端口和协议

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

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

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

//*.mywebsite.example:*/*

526824 次浏览

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


# 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.example作为ACCESS_CONTROL_ROOT,它将限制起源为sub.mydomain.example*.sub.mydomain.example(即它不必是域根)。可以通过修改正则表达式的URI匹配部分来控制允许变化的元素(协议、端口)。

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

SetEnvIf Origin ^(https?://.+\.mywebsite\.example(?::\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.example)以及它的所有子域上启用CORS的人可以简单地将第一行中的正则表达式替换为:

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

注意:对于规范遵从和正确的缓存行为,总是为cors启用的资源添加Vary: Origin响应头,即使对于非cors请求和来自不允许来源的请求(参见示例为什么)。

我需要一个只有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. regex分组是的性能影响,这是不必要的。
  2. 不能匹配主要的域,它只适用于子域。

例如:它不会为http://mywebsite.example发送CORS报头,而对http://somedomain.mywebsite.example/有效

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.example的位置。

允许多个站点:

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

部署后验证:

下面的curl响应应该具有“access - control - allow - origin”;头后的变化。

curl -X GET -H "Origin: http://site1.example" --verbose http://site2.example/query

我们也有类似的问题与字体Awesome静态quot;cookie-less"域,当从“;cookie”中读取字体时;(www.domain.example)这篇文章是我们的英雄。看这里:我如何修复'丢失跨源资源共享(CORS)响应头'webfont问题?

对于复制/粘贴-r类型(并提供一些道具),我从所有的贡献中拼凑起来,并将其添加到站点根文件的.htaccess文件的顶部:

<IfModule mod_headers.c>
<IfModule mod_rewrite.c>
SetEnvIf Origin "http(s)?://(.+\.)?(othersite\.example|mywebsite\.example)(:\d{1,5})?$" CORS=$0
Header set Access-Control-Allow-Origin "%{CORS}e" env=CORS
Header merge  Vary "Origin"
</IfModule>
</IfModule>

超级安全,超级优雅。喜欢它:你不必开放你的服务器带宽给资源小偷/热链接者类型。

看起来最初的答案是在Apache 2.4之前。这对我没用。下面是我为了让它在2.4中正常工作所做的修改。这将适用于yourcompany.example的子域的任何深度。

SetEnvIf Host ^((?:.+\.)*yourcompany\.example?)$    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"

当在.htaccess中设置Access-Control-Allow-Origin时,只有以下情况有效:

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

我尝试了其他几个建议的关键字Header appendHeader set没有工作,正如Stack Overflow上的许多答案所建议的那样,尽管我不知道这些关键字是否过时或对nginx无效。

以下是我的完整解决方案:

SetEnvIf Origin "http(s)?://(.+\.)?domain\.example(:\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]

我不得不稍微修改一下佬司的答案,因为一个孤立的\最终出现在正则表达式中,只比较实际的主机(不注意协议或端口),我想在我的生产域之外支持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']));
}

对于春天的引导,我找到了这个RegexCorsConfiguration,它扩展了官方的CorsConfiguration: https://github.com/looorent/spring-security-jwt/blob/master/src/main/java/be/looorent/security/jwt/RegexCorsConfiguration.java

在我的例子中使用的是angular

在我的HTTP拦截器,我设置

with Credentials: true.

在请求头中

对我来说,我想要一个多域选项,这就是我使用的解决方案,有pythonflask

    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、不同端口等),并在请求上进行检查。

与通配符解决方案相比,我更喜欢这种解决方案,因此这是我在运行的服务器上的选择。

对于Spring,你应该使用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

相比之下,allowedOrigins只支持““;当一个allowedOriginPattern被匹配时,Access-Control-Allow-Origin响应头被设置为匹配的原点,而不是""也不是对模式。因此,allowedOriginPatterns可以与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]));


}
};
}