关于cors:Safari中的跨源资源共享策略拒绝跨域重定向

Cross-origin redirection denied by Cross-Origin Resource Sharing policy in Safari

我们有一个API端点可以重定向到另一个服务器。它是通过XHR调用的,在大多数浏览器中似乎都可以正常工作,除了Safari(特别是iOS)。

我进入控制台的错误是:跨源资源共享策略拒绝跨源重定向

我们在执行重定向的页面和其他服务器上有CORS。重定向页设置:

1
2
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: false

另一台服务器具有:

1
Access-Control-Allow-Origin: *

如何在CORS策略中允许重定向?


W3C规范和其他权威来源直接禁止通配符Access-Control-Allow-OriginAccess-Control-Allow-Credentials: true一起使用。

Note: The string"*" cannot be used for a resource that supports credentials.

https://www.w3.org/tr/cors/资源请求

Important note: when responding to a credentialed request, server must specify a domain, and cannot use wild carding.

https://developer.mozilla.org/en-us/docs/web/http/access-control-cors-requests-with-u-credentials

If credentials mode is"include", then Access-Control-Allow-Origin cannot be *.

https://fetch.spec.whatwg.org/CORS协议和凭证

进一步步骤

因为你的问题缺乏细节,我们来做一些定义:

  • 域A是客户端代码的域
  • DOMAIN-B是向其发出请求的端点所在的域
  • 域C是请求最终重定向到的域

首先,我认为,你想做一个变通方案。只要你告诉所有的终点都在你的控制之下,那么你要么:

  • 从域A直接向域C发出请求(或者甚至发出条件请求,如果重定向取决于参数)
  • 公开后端中将请求包装到域B的另一个端点

如果一个bug真的违反了规范,那么向Webkit追踪器报告它也是很重要的。为了更容易地重现这个案例,我制作了一个Cherrypy应用程序,您可以将它附加到报告中。运行步骤:

  • 将"127.0.0.1域-a域-b域-c"添加到您的/etc/hosts
  • 将以下代码放入corsredirect.py
  • 在终端中运行这些命令

    1
    2
    3
    4
     virtualenv -p python3 venv
     . venv/bin/activate
     pip install cherrypy
     python corsredirect.py
  • 将浏览器指向http://domain-a:8080并按下按钮

  • 这是应用程序。

    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
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    #!/usr/bin/env python3
    '''
    Add localhost aliases in /etc/hosts for"domain-a","domain-b","domain-c".

    The flow is: [domain-a] --CORS-GET--> [domain-b] --redirect--> [domain-c].
    Open as http://domain-a:8080/
    '''


    import cherrypy


    def cors():
      cherrypy.response.headers['Access-Control-Allow-Origin'] = '*'

    cherrypy.tools.cors = cherrypy._cptools.HandlerTool(cors)


    class App:

      @cherrypy.expose
      def index(self):
        return '''<!DOCTYPE html>
          <html>
          <head>
          <meta content='text/html; charset=utf-8' http-equiv='content-type'>
          CORS redirect test
          </head>
          <body>
            <button>make request</button>
            <script type='text/javascript'>
              document.querySelector('button').onclick = function()
              {
                var xhr = new XMLHttpRequest();
                xhr.open('GET', 'http://domain-b:8080/redirect', true);
                xhr.onload = function()
                {
                  var text = xhr.responseText;
                  console.log('success', text);
                };
                xhr.onerror = function()
                {
                  console.error('failure');
                };
                xhr.send();
              };
           
          </body>
          </html>
        '''

      @cherrypy.expose
      @cherrypy.config(**{'tools.cors.on': True})
      def redirect(self):
        raise cherrypy.HTTPRedirect('http://domain-c:8080/endpoint')

      @cherrypy.expose
      @cherrypy.config(**{'tools.cors.on': True})
      @cherrypy.tools.json_out()
      def endpoint(self):
        return {'answer': 42}


    if __name__ == '__main__':
      config = {
        'global' : {
          'server.socket_host' : '127.0.0.1',
          'server.socket_port' : 8080,
          'server.thread_pool' : 8
        }
      }
      cherrypy.quickstart(App(), '/', config)


    你试过了吗?

    1
    2
    3
    Access-Control-Allow-Origin: *
    Access-Control-Allow-Headers:"Origin, X-Requested-With, Content-Type, Accept"
    Access-Control-Allow-Methods: *

    欲了解更多信息,请访问:http://enable-cors.org/


    如果您可以更改您的URL,您可以在Apache上尝试使用代理传递配置。呼叫看起来像在同一个域中,但不会。https://httpd.apache.org/docs/current/fr/mod/mod_proxy.html


    我在Heroku应用程序上托管API时遇到了类似的问题。在chrome和firefox上,来自另一个域对我的API的请求工作得很好,但在Safari上,我遇到了一个令人沮丧的"跨源重定向被跨源资源共享策略拒绝"。

    经过一些研究,似乎Safari中有一个bug阻止了一些CORS重定向。我可以通过直接请求Heroku应用程序(myapp.herokuapp.com/api)而不是我的域(mydomain.com/api)来解决这个问题。

    如果您有一个重定向到API的设置,直接请求根域可能会有所帮助。


    您需要设置"访问控制允许方法"头。

    1
    Access-Control-Allow-Methods: *


    在两个服务器上启用HTTPS已经为我解决了问题。


    对于除"简单请求"以外的任何内容,权威源拒绝"访问控制允许来源"头的通配符,并要求您显式设置头"访问控制允许来源"。

    下面是Mozilla对"简单请求"的声明:

    The only allowed methods are:

    • GET
    • HEAD
    • POST

    Apart from the headers set automatically by the user agent (e.g.
    Connection, User-Agent, etc.), the only headers which are allowed to
    be manually set are:

    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type

    The only allowed values for the Content-Type header are:

    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

    当您的请求不满足"简单请求"的要求时,您可能会陷入"预先点燃的请求"。

    methods other than GET, HEAD or POST. Also, if POST is used to send request data with a Content-Type other than application/x-www-form-urlencoded, multipart/form-data, or text/plain, e.g. if the POST request sends an XML payload to the server using application/xml or text/xml, then the request is preflighted.

    对于认证申请-

    Important note: when responding to a credentialed request, server must specify a domain, and cannot use wild carding. Access-Control-Allow-Origin: *

    由于您没有提供来自您的页面和服务器的实际HTTP请求和响应,因此我必须做出一些假设。我假设您的页面是在foo.example域下加载的,并且您的api是在同一foo.example域下的,而您的"其他"服务器是在域栏上的。

    您可能需要设置页面,以便使用这些头向"其他"服务器发出重定向请求:

    1
    2
    Access-Control-Request-Method: GET, OPTIONS
    Access-Control-Request-Headers: x-requested-with, Content-Type, CUSTOM-HEADER

    然后,您可能需要设置"其他"服务器以响应选项请求:

    1
    2
    3
    Access-Control-Allow-Origin: https://foo.example
    Access-Control-Allow-Methods: GET, OPTIONS
    Access-Control-Allow-Headers: x-requested-with, Content-Type, CUSTOM-HEADER

    那么您的页面应该能够完成请求。