编者注:这篇文章非常受欢迎!Fetch API 现已在浏览器中可用,并使跨域请求比以往更容易。查看这篇文章或上面的链接以了解更多信息。
XMLHttpRequest 用于许多 Ajax 库中,但直到 Firefox 3.5 和 Safari 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 代码。服务器管理员应谨慎处理泄露私人数据,并应明智地确定哪些资源可以跨域调用。
参考资料
- Mozilla Developer Wiki 上关于 CORS(以前称为访问控制)的文档
- 适用于服务器管理员的 Mozilla Developer Wiki 文档
- 跨域 XMLHttpRequest (XS-XHR) 示例
- Web 字体上下文中的 CORS 以及如何在 Apache 服务器上使用 .htaccess 来确保返回正确的 CORS 报头
46条评论