【问题标题】:Access-Control-Allow-Origin wildcard subdomains, ports and protocolsAccess-Control-Allow-Origin 通配符子域、端口和协议
【发布时间】:2012-12-09 19:30:01
【问题描述】:

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

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

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

//*.mywebsite.com:*/*

【问题讨论】:

    标签: cors


    【解决方案1】:

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

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

    【讨论】:

    • @Dexter "null" 可用于响应“null”来源,例如从 file:// 方案发出 CORS 请求时。
    • CORS 规范不支持 OP 的 exact 用例是非常短视的。
    • @aroth:不是真的,规范允许实现使用他们想要的 any 匹配语法; 实现 不支持这个用例只会是非常短视的。换句话说,您指定给服务器的不是 ACAO 值,后者只是协议细节。我的假设是,有一种安全场景需要回显原点或从中受益,但天真的实现只需要说“OK”或不说“OK”即可。
    • 2015 年更新:这个答案应该被认为是有害的,因为它不完整并且可能导致缓存问题。请在下面查看我的答案以获得正确的实现(对于 Apache)和解释:stackoverflow.com/a/27990162/357774。此外,@aroth,正如 tne 指出的那样,规范实际上 确实 允许 OP 的确切用例:w3.org/TR/cors/#resource-implementation。正如这个答案所指出的,由服务器来实现它。如上面提到的答案所示,这可以用 3 行来完成。
    • @Noyo - 我会澄清我原来的意思。 CORS 规范并没有严格要求所有实现 CORS 的服务器为 OP 的确切用例提供自动、内置的支持,这是非常短视的。让每个单独的用户使用自定义 PHP 代码、重写规则或您拥有的东西来构建自己的 shim,这是导致碎片、错误和灾难的秘诀。服务器开发人员应该比这更了解;如果他们不这样做,CORS 规范应该强制他们这样做。
    【解决方案2】:

    编辑:使用@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.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 匹配部分来控制允许变化的元素(协议、端口)。

    【讨论】:

      【解决方案3】:

      基于 DaveRandom 的 answer,我也在玩,发现了一个稍微简单的 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})?)$.

      注意:对于spec compliance 和正确的缓存行为,始终为启用 CORS 的资源添加 Vary: Origin 响应标头,即使对于非 CORS 请求和来自不允许来源的请求(请参阅example why)。

      【讨论】:

      • 当访问者使用相同的字体在多个子域之间跳转时,我们几乎有这个(不是 Vary Origin)并且出现了不良行为。字体和 access-control-origin 标头也被缓存。我对此做了一点改动:如果请求来自我们允许的域之一,我使用“Access-Control-Allow-Origin *”。也许这可以通过我们以前没有的“Vary Origin”解决......现在也添加了。
      • 不适用于主域“mywebsite.com”
      • @pgmann,在这种情况下没有必要转义 //,因为 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.
      • 你把这段代码放在哪里? .htaccess 还是在 apache 虚拟主机配置中?
      【解决方案4】:

      我需要一个仅限 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,编辑为使用 preg_quote(),因为这是正确的做法(尽管 . 是 DNS 名称中唯一有效的正则表达式元字符,preg_quote() 描述了预期的操作更好)
      • 根据规范,应该澄清none 不是标头的语义有效值(或者至少,不符合它的含义)。因此,return null; 可能对该分支更有意义,在这种情况下,不应将标头发送给客户端,因此调用者应检查它。
      • preg_quote() 将引用 * 符号,因此 str_replace() 例如留下一个孤立的“\”。
      • 这很有用,我花了一些时间在 CORS 问题上,直到我意识到我的网站在 ajax 中有“www”,但在永久链接结构中没有 - 您的解决方案帮助我了解了问题所在并解决了这个问题对我来说。
      【解决方案5】:

      我正在回答这个问题,因为已接受的答案无法关注

      1. 正则表达式分组会影响性能,这不是必需的。
      2. 不能匹配主域,它只适用于子域。

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

      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
      

      部署后验证:

      以下 curl 响应在更改后应具有“Access-Control-Allow-Origin”标头。

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

      【讨论】:

        【解决方案6】:

        在从“cookie 域”(www.domain.tld) 读取字体时,我们在静态“无 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\.com|mywebsite\.com)(:\d{1,5})?$" CORS=$0
            Header set Access-Control-Allow-Origin "%{CORS}e" env=CORS
            Header merge  Vary "Origin"
         </IfModule>
        </IfModule>
        

        超级安全,超级优雅。喜欢它:您不必为资源窃贼/热链接者类型开放服务器带宽。

        【讨论】:

          【解决方案7】:

          看起来最初的答案是针对 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"
          

          【讨论】:

            【解决方案8】:

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

            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

            这是我的完整解决方案:

            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]
            

            【讨论】:

              【解决方案9】:

              我不得不稍微修改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']));
              }
              

              【讨论】:

                【解决方案10】:

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

                【讨论】:

                  【解决方案11】:

                  在我的情况下使用角度

                  在我的 HTTP 拦截器中,我设置了

                  with Credentials: true.
                  

                  在请求的头部

                  【讨论】:

                    【解决方案12】:

                    对我来说,我想要一个多域选项,这是我使用的解决方案,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、不同的端口等),然后在请求中检查它。

                    我更喜欢这个解决方案而不是通配符解决方案,所以这是我在我运行的服务器上的选择。

                    【讨论】:

                      猜你喜欢
                      • 2011-07-28
                      • 2017-03-05
                      • 2011-09-02
                      • 2016-09-30
                      • 1970-01-01
                      • 2016-08-03
                      • 2013-10-28
                      相关资源
                      最近更新 更多