Access-Control-Allow-Origin 多个源域名?

1367

有没有一种方法可以使用 Access-Control-Allow-Origin 标头允许多个跨域?

我知道可以用 *,但这太宽松了。我真正想要的是只允许几个域名。

例如,像这样:

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

我尝试了上面的代码,但似乎在Firefox中无法正常工作。

是否可以指定多个域名,还是只能使用一个?


17
与其允许用空格分隔的多个来源,(origin-list-or-null) 要么是单个来源,要么是字符串 "null"。 - sam
5
使用最新版本的Firefox浏览器,逗号分隔或空格分隔的域名都无法正常工作。相较而言,将域名列表进行匹配并在头部加入单个主机仍然是更好的安全方式,并且可以正常工作。 - Daniel W.
2
如果您在处理HTTPS时遇到困难,我找到了一个解决方案:[https://dev59.com/O3I-5IYBdhLWcg3w0cOG#28552592]。 - Alex W
@sam 还是 "*"?基于返回 Origin 请求头的值、"*" 或 "null" 的共享 - Nate Anderson
22
重要提醒:在Access-Control-Allow-Origin头中仅允许特定域并不意味着其他域不能触发此端点上的方法(例如REST API方法)。这只是意味着不允许的来源不能在JavaScript中使用结果(浏览器会确保这一点)。为了限制特定域对端点的访问,请使用服务器端请求过滤器,例如返回HTTP 401以拒绝不允许的域。 - klues
8
当您想要使用多个URL时,应始终附加Vary: Origin头部,参见:https://fetch.spec.whatwg.org/#cors-protocol-and-http-caches。 - Null
35个回答

1053

听起来推荐的做法是让你的服务器读取客户端发送的Origin头信息,将其与允许的域名列表进行比较,如果匹配成功,则在响应中将Origin头信息的值作为Access-Control-Allow-Origin头信息回显给客户端。

使用.htaccess可以像这样实现:

# ----------------------------------------------------------------------
# Allow loading of external fonts
# ----------------------------------------------------------------------
<FilesMatch "\.(ttf|otf|eot|woff|woff2)$">
    <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>

你如何添加通配符子域名,例如:*.example.com 或通配符端口,例如:localhost:* - Ben Winding
8
对于任何想知道的人,你可以使用 (.+\.google.com) 替代 (google.com|staging.google.com) - Ben Winding
2
如果没有匹配,这个会怎么表现?Access-Control-Allow-Origin的输出会是什么? - Héctor
8
这个正则表达式设计得不够好,特别是不应该允许使用不安全的来源(使用 http 方案),并且 DNS 标签分隔符应该被转义(使用 \. 而不是 .)。否则,攻击者可以购买 developmentzgoogle.com 域名,并从那里发起跨源攻击。 - jub0bs
@Héctor 在这种情况下,它将只是像这个解决方案从未被引入一样行事 - 只会阻止来自不匹配域的所有请求。 - biesior

285

我在 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");
}

5
HTTP_ORIGIN 不可靠,请参考 https://dev59.com/ap3ha4cB1Zd3GeqPVH30。 - bareMetal
1
@bareMetal 你的评论只是部分正确。虽然确实存在Origin HTTP 头为 null的情况,但也要注意到这个头部在默认的CORS机制中用于允许或拒绝请求。这里提到的实现确保了指定来源的严格匹配(==),并在没有匹配时拒绝请求(即将null视为非匹配)。这种方法被认为是一种安全的实现。 - Advena

134

对我有用的是这个:

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中,它一定会起作用。


96

我遇到了与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 中的正则表达式即可。


有多个域名的示例非常方便:^(https?:\/\/localhost:\d+)$|^(https?:\/\/.+\.yourdomain\.com)$。以下是它的实际应用... https://regex101.com/r/GZHTLB/1。这看起来很混乱,但 regex101 网站可以帮助解密它。 - chichilatte

78

以下是如何在Nginx中回显符合您域名要求的"Origin"头信息,这在您需要为多个子域名提供字体时非常有用:

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

58

对于 ExpressJS 应用程序,您可以使用:

app.use((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();
});

1
正是我所需要的。我几乎做对了 :) 现在你可以简化为 corsWhiteList.includes(req.headers.origin) - Tomáš Zato
https://www.npmjs.com/package/cors#configuring-cors-w-dynamic-origin - M Imam Pratama

38

这是我为一个通过AJAX请求的PHP应用程序所做的工作

$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头的值返回,而不是返回*通配符。

1
最好检查一下$request_headers['Origin'];是否存在,否则任何直接请求都会触发E_NOTICE。 - MrWhite

26

如上所述,Access-Control-Allow-Origin 应该是唯一的,如果你在 CDN (内容分发网络) 后面,Vary 应该设置为 Origin

我的 Nginx 配置的相关部分:

if ($http_origin ~* (https?://.*\.mydomain\.com(:[0-9]+)?)) {
  set $cors "true";
}
if ($http_origin ~* (https?://.*\.my-other-domain\.com(:[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';
}

任何域名中的 . 都需要转义,否则它将匹配任何字符。此外,您可以只使用一个 if 语句而不是使用变量。 - suddjian

25

需要注意的一点缺陷是:一旦您将文件外包到CDN(或任何不允许脚本的其他服务器)上,或者如果您的文件被代理缓存,基于“Origin”请求头更改响应就不起作用了。


25

对于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”标头。


警告:https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/ - Almenon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接