跨域 XMLHttpRequest 与 CORS

编者注:这篇文章非常受欢迎!Fetch API 现已在浏览器中可用,并使跨域请求比以往更容易。查看这篇文章或上面的链接以了解更多信息。

XMLHttpRequest 用于许多 Ajax 库中,但直到 Firefox 3.5Safari 4 等浏览器的发布,它只能在JavaScript 同源策略框架内使用。这意味着使用 XMLHttpRequest 的 Web 应用程序只能向加载它的域发出 HTTP 请求,而不能向其他域发出请求。开发人员表达了希望安全地发展 XMLHttpRequest 等功能以进行跨域请求的愿望,以便在 Web 应用程序中实现更好、更安全的混搭。 跨域资源共享 (CORS) 规范包括客户端和服务器之间简单的报头交换,并由 IE8 专有的 XDomainRequest 对象以及 Firefox 3.5 和 Safari 4 等浏览器中的 XMLHttpRequest 用于进行跨域请求。这些浏览器允许在脚本中对其他域进行异步 HTTP 调用,只要检索到的资源以适当的 CORS 报头返回。

CORS 的快速概述

Firefox 3.5 和 Safari 4 实现 CORS 规范,使用 <a href="https://mdn.org.cn/En/XMLHttpRequest">XMLHttpRequest</a> 作为“API 容器”,代表 Web 开发人员发送和接收适当的报头,从而允许跨域请求。IE8 实现 CORS 规范的一部分,使用 <a href="http://msdn.microsoft.com/en-us/library/cc288060%28VS.85%29.aspx">XDomainRequest</a> 作为类似的“API 容器”用于 CORS,从而实现简单的跨域 GET 和 POST 请求。值得注意的是,这些浏览器会发送 ORIGIN 报头,它提供发出跨域请求的页面的方案 (http:// 或 https://) 和域。

CORS 标准通过添加新的 HTTP 报头来工作,这些报头允许服务器将资源提供给允许的源域。浏览器支持这些报头并执行它们所建立的限制。此外,对于可能对用户数据造成副作用的 HTTP 请求方法(特别是对于 GET 以外的 HTTP 方法,或对于使用某些 MIME 类型的 POST 使用情况),规范要求浏览器“预检”请求,使用 HTTP OPTIONS 请求报头从服务器请求支持的方法,然后,在服务器“批准”后,使用实际的 HTTP 请求方法发送实际请求。服务器还可以通知客户端是否应将“凭据”(包括 Cookie 和 HTTP 身份验证数据)与请求一起发送。

功能检测

XMLHttpRequest 可以在 Firefox 3.5 和 Safari 4 中发出跨域请求;在这些浏览器的早期版本中,跨域请求将失败。始终可以先尝试启动跨域请求,如果失败,则可以得出结论,所讨论的浏览器无法处理来自 XMLHttpRequest 的跨域请求(基于处理失败条件或异常,例如未获得200 status 代码)。在 Firefox 3.5 和 Safari 4 中,如果服务器不提供适当的 CORS 报头(特别是 Access-Control-Allow-Origin 报头)与资源一起返回,跨域 XMLHttpRequest 将无法成功获取资源,尽管请求会通过。在旧版浏览器中,尝试进行跨域 XMLHttpRequest 将简单地失败(根本不会发送请求)。

Safari 4 和 Firefox 3.5 都在 XMLHttpRequest 上提供了 withCredentials 属性,以符合新兴的 XMLHttpRequest 2 级 规范,这可以用来检测实现 CORS 的 XMLHttpRequest 对象(因此允许跨域请求)。这允许进行方便的“对象检测”机制

if (XMLHttpRequest)
{
    var request = new XMLHttpRequest();
    if (request.withCredentials !== undefined)
    {
      // make cross-site requests
    }
}

或者,您也可以使用“in”运算符

if("withCredentials" in request)
{
  // make cross-site requests
}

因此,withCredentials 属性可以在功能检测的上下文中使用。我们将在本文后面讨论使用“withCredentials”作为将 Cookie 和 HTTP-Auth 数据发送到网站的一种方法。

使用 GET 或 POST 的“简单”请求

IE8、Safari 4 和 Firefox 3.5 允许简单的 GET 和 POST 跨域请求。“简单”请求不会设置自定义报头,并且请求主体只使用纯文本(即 text/plain Content-Type)。

假设以下代码片段是从 http://foo.example 上的一个页面提供的,并且正在调用 http://bar.other

var url = "http://bar.other/publicNotaries/"
if(XMLHttpRequest)
{
  var request = new XMLHttpRequest();
  if("withCredentials" in request)
  {
   // Firefox 3.5 and Safari 4
   request.open('GET', url, true);
   request.onreadystatechange = handler;
   request.send();
  }
  else if (XDomainRequest)
  {
   // IE8
   var xdr = new XDomainRequest();
   xdr.open("get", url);
   xdr.send();

   // handle XDR responses -- not shown here :-)
  }

 // This version of XHR does not support CORS
 // Handle accordingly
}

Firefox 3.5、IE8 和 Safari 4 负责发送和接收正确的报头。以下是简单请求示例。查看服务器返回的报头也很有启发性。值得注意的是,在其他请求报头中,浏览器会发送以下内容以启用上面的简单请求

GET /publicNotaries/ HTTP/1.1
Referer: http://foo.example/notary-mashup/
Origin: http://foo.example

请注意使用“Origin” HTTP 报头,它是 CORS 规范的一部分。

此外,在其他响应报头中,http://bar.other 上的服务器将包含

Access-Control-Allow-Origin: http://foo.example
Content-Type: application/xml
......

可以在 Mozilla Developer Wiki 上找到关于 CORS 和 XMLHttpRequest 的更完整论述。

“预检”请求

CORS 规范要求使用 POST 或 GET 以外的方法、使用自定义报头或请求非 text/plain 类型的请求主体的请求要进行预检。预检请求首先将 OPTIONS 报头发送到另一个域上的资源,以检查并查看是否可以安全地发送实际请求。此功能目前不受 IE8 的 XDomainRequest 对象支持,但 Firefox 3.5 和 Safari 4 使用 XMLHttpRequest 支持。Web 开发人员无需担心预检机制,因为实现会处理它。

以下代码片段显示了 http://foo.example 上的一个网页调用 http://bar.other 上的资源的代码。为简单起见,我们省略了对象和功能检测部分,因为我们已经介绍了它

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/post-here/';
var body = '
Arun';
function callOtherDomain(){
if(invocation)
{
    invocation.open('POST', url, true);
    invocation.setRequestHeader('X-PINGOTHER', 'pingpong');
    invocation.setRequestHeader('Content-Type', 'application/xml');
    invocation.onreadystatechange = handler;
    invocation.send(body);
}

您可以在这里查看此示例。查看客户端和服务器之间的报头交换真的很有启发性。可以在 Mozilla Developer Wiki 上找到对此的更详细论述。

在这种情况下,在 Firefox 3.5 发送请求之前,它首先使用 OPTIONS 报头

OPTIONS /resources/post-here/ HTTP/1.1
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER

然后,在其他响应报头中,服务器会用以下内容进行响应

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://arunranga.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER
Access-Control-Max-Age: 1728000

此时,会发送实际的响应

POST /resources/post-here/ HTTP/1.1
...
Content-Type: application/xml; charset=UTF-8
X-PINGOTHER: pingpong
...

带凭据的请求

默认情况下,Cookie 和 HTTP Auth 信息等“凭据”不会在使用 XMLHttpRequest 的跨域请求中发送。要发送它们,您必须设置 XMLHttpRequest 对象的 withCredentials 属性。这是 Firefox 3.5 和 Safari 4 中引入的一个新属性。IE8 的 XDomainRequest 对象不具备此功能。

同样,假设 http://foo.example 上一个页面中的某些 JavaScript 希望调用 http://bar.other 上的资源并将 Cookie 与请求一起发送,以便响应知道用户可能已获取的 Cookie。

var request = new XMLHttpRequest();
var url = 'http://bar.other/resources/credentialed-content/';
function callOtherDomain(){
  if(request)
  {
   request.open('GET', url, true);
   request.withCredentials = "true";
   request.onreadystatechange = handler;
   request.send();
  }

请注意,withCredentials 默认情况下为 false(并且未设置)。报头交换类似于简单 GET 请求的情况,不同之处在于现在会将 HTTP Cookie 报头与请求报头一起发送。您可以在这里查看此示例

关于安全性的说明

通常,从远程站点请求的数据应视为不可信数据。不建议在先确定其有效性之前执行从第三方站点检索的 JavaScript 代码。服务器管理员应谨慎处理泄露私人数据,并应明智地确定哪些资源可以跨域调用。

参考资料

关于 Arun Ranganathan

更多 Arun Ranganathan 的文章…


46条评论

  1. William Edney

    Arun -

    感谢您提供极佳的示例。我获取了“简单示例”页面,将其保存到我的文件系统中,使用“file://”URL 将该页面重新加载到另一个窗口中,并尝试调用跨站点查询。这在 Firefox 3.5(Mac)和 Safari 4(Mac)中均失败。

    是否有原因导致它无法正常工作?鉴于 W3 将“file://”URL 定义为有效来源,恕我直言,它应该可以正常工作。

    干杯,

    - 比尔

    2009 年 7 月 6 日 下午 4:41

  2. William Edney

    Arun -

    好吧,也许我是一个白痴,您的服务器只授权了您在示例中调用的域,而不是“*”。

    嗯...

    干杯,

    - 比尔

    2009 年 7 月 6 日 下午 4:48

  3. thinsoldier

    一直在等待一整天的更新!

    :) 谢谢。

    2009 年 7 月 6 日 下午 5:05

  4. Jeff Walden

    拼写错误:“跨域资源共享”,而不是“请求”共享。

    2009 年 7 月 6 日 晚上 7:38

  5. Gen Kanai

    @Jeff - 感谢您的编辑。已更新。

    2009 年 7 月 6 日 晚上 8:55

  6. Arun Ranganathan

    @Bill - 好问题 :) 当您获取简单请求并在本地运行它(从 file:///)时,Origin 标头的值现在为 null(“Origin: null”)。但是,我的服务器端 PHP 脚本不处理 null Origin,因此不会发送回正确的响应。

    @Jeff - 哎呀,抓住了 :)。

    2009 年 7 月 6 日 晚上 11:15

  7. Firefox Fanatic

    因此,Firefox 3.5、Safari 4 和 Google Chrome 2 都支持 CORS 规范,而 Internet Explorer 8 部分支持它,使用 XDomainRequest。Opera 呢?他们什么时候会支持此功能?

    2009 年 7 月 7 日 上午 8:35

  8. Arun Ranganathan

    @FirefoxFanatic - Opera 尚未对此发表评论;我们从 Opera 工程师那里收到的最后一条公开消息是:http://lists.w3.org/Archives/Public/public-webapps/2009AprJun/1223.html

    2009 年 7 月 7 日 上午 9:42

  9. William Edney

    Arun -

    对这里发生的事情进行了更多调查。

    1. 我是一个白痴,只有在发布后我才意识到您的服务器没有配置为“Access-Control-Allow-Origin: *”。

    2. 我找到了一个支持该功能的测试服务器

    http://rockstarapps.com/test.php.

    3. 对该服务器测试了 FF 3.5 和 Safari 4.X。事实证明,Safari 4.X 运行正常,FF 3.5 不正常。去 Bugzilla...

    谢谢!

    干杯,

    - 比尔

    2009 年 7 月 7 日 上午 10:53

  10. William Edney

    Arun -

    最后一条消息。在为 FF 3.5 缩减测试用例时,我发现我之前的测试中存在错误。FF 3.5 运行良好。尚未在 IE8 中尝试过 :-)。

    再次感谢您提供这些有用的示例 :-)。

    干杯,

    - 比尔

    2009 年 7 月 7 日 上午 11:23

  11. […] 跨站点 xmlhttprequest 与 CORS 站点间的 xmlhttp 交互 (标签:javascript ajax) […]

    2009 年 7 月 8 日 上午 6:30

  12. […] 现在,Firefox 3.5 已经实现了该改进,使我们能够使用它。另一方面,Microsoft 在另一个世界中开发了 XDomainRequest(),它允许进行 […]

    2009 年 7 月 8 日 晚上 11:06

  13. […] 现在,Firefox 3.5 已经实现了该改进,使我们能够使用它。另一方面,Microsoft 在另一个世界中开发了 XDomainRequest(),它允许进行 […]

    2009 年 7 月 8 日 晚上 11:20

  14. […] Google Chrome 2 以及现在的 Firefox 3.5 已经实现了该改进,使我们能够使用它。另一方面,Microsoft 在另一个世界中开发了 XDomainRequest(),它允许进行 […]

    2009 年 7 月 9 日 上午 0:01

  15. […] 允许在线服务之间更好地集成。Safari4、Google Chrome 2 以及现在的 Firefox 3.5 已经实现了该改进,使我们能够使用它。另一方面,Microsoft 在另一个世界中开发了 XDomainRequest(),它允许进行 […]

    2009 年 7 月 9 日 上午 2:54

  16. […] 使我注意到了新的 Firefox 3.5+ CORS(跨域资源共享),这是一种执行跨域 XMLHTTPReqest 的方法。听起来可怕吗?嗯,确实如此,但 […]

    2009 年 7 月 20 日 下午 12:07

  17. […] 不是要谈论其实现本身,因为我写不出比 hacks.mozilla 写的更好的内容,而是要谈论 Microsoft 再次破坏了标准(实际上,仔细看看这篇文章 […]

    2009 年 8 月 27 日 下午 2:31

  18. Xiaoxin

    使用 Chrome 测试了 CORS,它可以正常工作,但是 xhr.withCredentials 始终返回未定义,这使得这种特性检测方法不可靠。在 Chrome 2.0.172.43 上测试。

    2009 年 8 月 31 日 上午 9:01

  19. […] 在过去五年中,一件显而易见的事情是,现代浏览器(Firefox、Safari、Opera 和 Chrome)与世界上最流行的浏览器 IE 之间出现了巨大的差距。现代浏览器为 Web 应用程序的未来而构建 - 超快的 JavaScript、现代 CSS、HTML5、对各种 Web 应用程序标准的支持、可下载字体支持、离线应用程序支持、通过 Canvas 和 WebGL 的原始图形、原生视频、先进的 XHR 功能与新的安全工具和网络功能相结合。 […]

    2009 年 11 月 9 日 上午 2:53

  20. […] 我偶然发现了 Mozilla Hacks 博客上的这篇文章。跨域资源共享 (CORS)。太棒了!最终一个真正的 […]

    2009 年 12 月 2 日 上午 7:05

  21. […] Mozilla 团队在其关于 CORS 的文章中建议,您应该检查 withCredentials 属性是否存在,以确定浏览器 […]

    2010 年 5 月 25 日 下午 6:37

  22. Nathan Friedly

    在跨域请求中调用 getAllResponseHeaders() 和 getResponseHeader() 时,我总是得到 null 或 ""。这是为什么?我该如何读取标头?

    2010 年 5 月 26 日 下午 1:50

  23. […] 跨域资源共享 应用程序可以在一个站点中提供,并将数据发布到其他地方。 […]

    2010 年 7 月 20 日 上午 6:41

  24. WebDAV

    我们已经在 Firefox 3.6、Chrome 5 和 Safari 5 中测试了 CORS,发现只有 Chrome 可以正确处理对具有身份验证的服务器的请求。我们测试了带有 Basic、Digest 和 NTLM 的跨域 PROPFIND 请求,发现 Firefox 只支持 Digest 身份验证(出于某种原因,它不支持带有 SSL 的 PROPFIND 的 Basic 身份验证),而 Safari 根本不支持任何 PROPFIND 请求的身份验证。

    这非常令人失望,因为 PROPFIND 和其他 WebDAV 动词对于我们的产品至关重要,希望他们能修复它。

    我们已在此发布结果:http://www.webdavsystem.com/ajaxfilebrowser/programming/cross_domain。请参阅页面底部的跨域请求与身份验证部分。

    2010 年 7 月 29 日 上午 8:43

    1. Christopher Blizzard

      您是否有此问题的测试用例?它应该可以正常工作。

      2010 年 8 月 16 日 下午 2:59

      1. Vladimir Lichman

        Christopher,我们已在此发布了一个错误
        https://bugzilla.mozilla.org/show_bug.cgi?id=597301

        其中详细描述了如何重现它。

        2010 年 9 月 16 日 晚上 7:34

  25. Intekhab

    您好 Arun,

    您的文章对理解跨域调用的概念很有帮助。我试了一下,但当我调用托管在另一台机器上的另一个 wb 站点上的 Web 服务(带有 webHttpbinding 的 WCF)时,我遇到了 403 禁止错误,状态为 0,就绪状态为 4。

    实际场景
    带有 Httpbinding 的 WCF
    使用 XMLHTTP 对象的 Ajax 调用
    POST 方法
    预检请求,因为内容类型为 application/Json

    感谢您的帮助

    Intekhab

    2010 年 11 月 16 日 上午 10:30

  26. kn33ch41

    如果有人在使用 withCredentials 发送 cookie 时遇到问题,请记住,Access-Control-Allow-Origin 必须指定一个与这些 cookie 相对应的有效域;通配符不起作用。

    此外,对于使用 XHR2 异步发送文件的任何人,请记住,Chrome 在发送 base64 编码的流(例如)时,默认情况下会设置 Content-Type 标头,这必须在服务器的预检 Access-Control-Allow-Headers 响应中指定为允许的标头。但是,Firefox 不需要这样做。

    2010 年 12 月 15 日 上午 6:13

  27. Nizzy

    使用 CORS,为什么 getAllResponseHeaders() 返回 null?

    有什么想法吗?

    谢谢
    Nizzy

    2010 年 12 月 30 日 上午 0:44

  28. […] 稳健的软件:使用 CORS 进行跨站点 XMLHttpRequest; […]

    2011 年 1 月 27 日 上午 3:31

  29. Demetris

    您说“Web 开发人员无需担心预检的机制,因为实现会处理它”。您指的是自动生成预检请求的客户端(浏览器)吗?我认为是。服务器是否也总是这样?我知道 Jetty 有一个配置来处理预检请求,但在大多数其他情况下,我都是由用户定义的 servlet 处理预检响应。您怎么看?

    谢谢

    2011 年 1 月 29 日 上午 8:13

  30. Demetris

    此外,我使用本地代理拦截了 CORS 预检请求,检查了 OPTIONS 标头,然后以应有的方式返回了响应(允许来源等的标头,使用 rn 终止它们)。浏览器上没有任何反应 - 这是怎么回事?使用 Android 上的 Chrome。

    2011 年 1 月 29 日 上午 8:18

  31. […] 尝试将 Apache 配置为远程服务器的代理,以允许使用 CORS 进行跨域 AJAX。为了实现这一点,我需要 Apache 响应 2 个 HTTP 动词,例如 […]

    2011 年 2 月 14 日 上午 10:24

  32. […] 在工作中开始使用手机(通过 PhoneGap Build 和 Jo)并最近开始在应用程序中使用 XHR 进行登录。 PhoneGap 通过 CORS 以某种方式启用了这一点(这是我的理解,如果错误请更正),这允许通过交换列出受信任来源等的标头来进行跨域资源共享。 只是出于好奇对它有了一些简单的了解。 […]

    2011 年 4 月 10 日 下午 03:52

  33. […] 你不关心某些浏览器(即 != Firefox 3.5,Safari 4,Chrome 2),你可以在 Access-Control-Allow-Origin: * 的形式中添加 CORS 响应标头。 但话说回来,如果你有控制权 […]

    2011 年 5 月 20 日 下午 15:57

  34. Totti Anh Nguyen

    感谢您提供清晰的 Javascript 代码示例来演示该功能!

    2011 年 5 月 25 日 下午 03:39

  35. […] 一个没有 JSONP 支持的 API,跨域障碍很快就会变得难以克服。 CORS 正在逐渐成为一种可行的替代方案,但它要求远程服务通过 […] 来支持它。

    2011 年 8 月 2 日 上午 08:03

  36. Max

    感谢您的信息!这可能是我要找的东西

    我有一个 HTTP 页面需要对一个安全 URL 执行 AJAX POST。 两个都在同一个域上。
    CORS 允许我这样做吗?

    我还可以完全控制页面加载的 JS,因此我甚至可以将 JS 文件托管在安全(HTTPS)环境中。

    你怎么看?

    2011 年 10 月 19 日 下午 11:34

  37. santhosh kumar

    我遇到了同样的跨域问题…… 我不确定我是否正确设置了标头

    http://stackoverflow.com/questions/7747695/cross-domain-issue-xmlhttp

    我漏掉了什么吗?

    2011 年 10 月 19 日 下午 16:50

  38. Zain Shaikh

    我在 Firefox 3.0.1 中尝试了提供的示例。
    http://arunranga.com/examples/access-control/preflightInvocation.html

    但我收到此错误

    “拒绝访问受限制的 URI”代码:“1012

    有什么解决方法吗???

    2011 年 12 月 2 日 上午 06:22

  39. […] Mozilla 团队在其关于 CORS 的文章中建议,您应该检查 withCredentials 属性是否存在,以确定浏览器 […]

    2011 年 12 月 14 日 上午 06:59

  40. Ranjan

    您好 Arun,

    我尝试了您的示例文件,它运行良好。
    但是,当我尝试使用我们服务器上的不同 URL 执行相同的操作时,它不起作用。

    您能告诉我为什么它不起作用吗?

    Ranjan

    2012 年 6 月 20 日 上午 06:19

  41. Kristoffer S

    这救了我一命。 谢谢!

    2012 年 10 月 10 日 上午 01:44

  42. kishore

    当我使用跨域 XMLHTTP 请求时,它在 Fire Fox 中运行良好。 但相同的代码在 Chrome 中显示 403 禁止。
    请问大家有什么建议可以解决这个问题吗?

    2012 年 10 月 29 日 下午 03:21

  43. Hridya

    您好,

    我尝试使用您的代码访问我的 Web 服务。 它在 Chrome 和 IE 中运行良好。

    但 Firefox 给出了 405 方法不允许错误。

    你能帮我解决这个问题吗?

    提前感谢

    2013 年 3 月 7 日 上午 00:09

  44. Nathan Friedly

    Hridya:这可能意味着 Firefox 正在使用 OPTIONS 请求对您的请求进行预检,而您的 Web 服务器不支持这些请求。 您可以通过不添加 cookie (withCredentials=false) 以及不设置任何标头来移除预检。

    2013 年 3 月 7 日 下午 16:24

本文的评论已关闭。