我正在尝试为所有子域、端口和协议启用 CORS。
例如,我希望能够运行从 http://sub.mywebsite.example:8080/
到 https://www.mywebsite.example/
的 XHR 请求*
通常,我想启用来自源匹配的请求(并且仅限于):
//*.mywebsite.example:*/*
CORS 规范是全有或全无。它只支持 *
、null
或确切的协议 + 域 + 端口:http://www.w3.org/TR/cors/#access-control-allow-origin-response-header
您的服务器将需要使用正则表达式验证原始标头,然后您可以在 Access-Control-Allow-Origin
响应标头中回显原始值。
基于 DaveRandom 的 answer,我也在玩,发现了一个稍微简单的 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})?)$
。
注意:对于 spec compliance 和正确的缓存行为,始终为启用 CORS 的资源添加 Vary: Origin
响应标头,即使对于非 CORS 请求和来自不允许来源的请求(请参阅 example why)。
//
,因为 Apache conf 不使用斜杠分隔的正则表达式。 Regexr 抱怨是因为在这种情况下,斜线作为分隔符具有特殊含义。
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.
使用 @Noyo's solution 而不是这个。它更简单、更清晰,并且在负载下可能性能更高。
原始答案仅用于历史目的!
我对这个问题做了一些尝试,并想出了这个可与 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.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 匹配部分来控制允许变化的元素(协议、端口)。
我正在回答这个问题,因为接受的答案不能跟随
正则表达式分组会影响性能,这不是必需的。无法匹配主域,它仅适用于子域。
例如:它不会为 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
我需要一个仅限 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'),
preg_quote()
,因为这是正确的做法(即使 .
是 DNS 名称中唯一有效的正则表达式元字符,preg_quote()
更好地描述了预期的操作)
none
不是标头的语义有效值(或者至少,不执行它所暗示的)。因此,return null;
可能对该分支更有意义,在这种情况下,不应将标头发送到客户端,因此调用者应检查它。
preg_quote()
将引用 * 符号,因此 str_replace()
例如留下一个孤立的“\”。
对于 Spring Boot,我发现这个 RegexCorsConfiguration
扩展了官方 CorsConfiguration
:https://github.com/looorent/spring-security-jwt/blob/master/src/main/java/be/looorent/security/jwt/RegexCorsConfiguration.java
在 .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 append
、Header set
、none work,正如 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]
当从“cookie 域”(www.domain.example
)读取字体时,我们在静态“无 cookie”域上的 Font Awesome 遇到了类似的问题,这篇文章是我们的英雄。见这里:How can I fix the 'Missing Cross-Origin Resource Sharing (CORS) Response Header' webfont issue?
对于 copy/paste-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"
我不得不稍微修改 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']));
}
对我来说,我想要一个多域选项,这是我使用的解决方案,带有 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、不同的端口等),然后在请求中检查它。
我更喜欢这个解决方案而不是通配符解决方案,所以这是我在我运行的服务器上的选择。
在我的情况下使用角度
在我的 HTTP 拦截器中,我设置
with Credentials: true.
在请求的头部
不定期副业成功案例分享