跨域请求详解
同源策略
Ajax的一个限制是同源策略(same origin policy),它要求所有请求必须来自同一个域名、子域名,并且地址的端口也应当一致。主要原因是处于安全考虑:因为当一个ajax请示被发送,所有的请求都会附带主域的cookie信息一起发送。也就是说,对于远程服务来讲,请求如果是来自于登陆后的用户,若没有同源策略的限制,攻击者就有可能获取你的Gmail里的邮件、得到你的 Fackbook 状态或者你 Twitter 中的好友,这是一个非常严重的安全性问题。
但是,尽管出于安全问题的考虑而提出了同源策略,这也对那些的确需要跨域获取合法数据的开发者造成了一些不方便。诸如Adobe Flash和JAVA之类的技术已经着手解决了这个问题,通常是使用跨域策略文件。现在针对Ajax,也提出了CORS(cross-origin resource sharing)的标准规范。
CORS
CORS打破同源策略的限制,赋予了前端代码访问可信的远程服务的权限。主流的浏览器都很好的支持这个规范,除非使用IE6,基本上可以很好地使用它。
支持CORS的浏览器:
- IE>=8(需要安全caveat)
- Firefox>=3
- Safari:完全支持
- Chrome:完全支持
- Opera:不支持
CORS的使用非常简单。如果想将你的服务器添加为受信任的数据源,只需在HTTP协议的响应头里加几行:
Access-Control-Allow-Origin:example.com
Access-Control-Request-Method:GET,POST
这两个头字段会对来自example.com的跨域GET和POST请求做验证。多个值之间用逗号分隔,就像上面提到的GET,POST值一样。如果要添加多个域名,将域名列在 Access-Control-Allow-Origin头字段之中,每两个域名之间用逗号分隔。如果允许来自任意域的访问请求,则需要在源中加入通配符(*)。
对于有些浏览器,比如Safari,它会首先发起一个OPTIONS请求以检查服务器是否允许跨域的请求。
另一方面,Firefox则会直接发起跨域请求,但当服务器没有配置CORS头字段时会抛出一个安全异常。需要注意一下这种浏览器行为的不同。
你甚至可以使用 Access-Control-Allow-Headers头字段来认证自定义的请求头:
Access-Control-Request-Headers:Authorization
这也意味着客户端可以在Ajax请求中添加自定义头,比如使用开放认证(OAuth)对请求进行签名:
var req=new XMLHttpRequest();
req.open("POST","/endpoint",true);
req.setRequestHeader("Authorization",oauth_signature);
XDomainRequest
不幸的是,尽管CORS是可以正常工作在IE8及更高版本的IE中的,微软还是选择另辟蹊径,不兼容规范且对W3C工作组制定的标准视而不见。微软使用了一个自己的对象XDomainRequest,用来代替XMLHttpRequest进行跨域通信。它的接口和XMLHttpRequest的非常像,它包含一系列约束和限制,比如只支持GET和POST方法,不支持验证和自定义字段,并且支持“Content-Type:text/plain"类型的请求。
如果你满足来这些限制条件,就可以在IE8中使用正确的Access-Control头字段和XDomainRequest来实现CORS,网页可以直接在浏览器中生成跨域数据请求,而不必使用服务器到服务器的请求。跨域请求需要经过网页和服务器的相互同意。通过利用window对象创建XDomainRequest对象,并打开到特定域的连接,可以在网页中启动一个跨域请求。浏览器将通过发送带有源值的Origin请求头,从特定域的服务器中请求数据。如果服务器响应的Access-Control-Allow-Origin响应头为*或请求页面的确切URL,则浏览器将完成连接,然后可以使用XDomainRequest跨域请求目标服务器上的数据。XDomainRequest使用如下所示:
// 1. Create XDR object: var xdr = new XDomainRequest(); // 2. Open connection with server using GET method: xdr.open("get", "http://www.contoso.com/xdr.aspx"); // 3. Send string data to server: xdr.send();
xdr.onload=function(){
var data=JSON.parse(xdr.responseText);
};
XDomain支持的方法有onload,onerror,ontimeout,onprogress,timeout。
JSONP
JSONP(JSON with padding)很早之前就被标准化了,甚至在CORS之前。这是另一种从远程服务器抓取数据的方式。原理是通过创建一个script标签,所请求的外部文件包含一段JSON数据,数据是由服务器所返回的,作为参数包装在一个函数调用中。script标签获取脚本文件并不受跨域的限制,所有浏览器都支持这种技术。
下面的例子是一个script标签指向一个远程服务:
<script src="http://example.com/data.json"></script>
所请求的文件data.json中包含一个JSON对象,这个对象包装在一个函数调用中:
jsonCallback({"data":"foo"})
这时我们定义一个全局函数,当加载脚本后,这个函数就会被调用:
window.jsonCallback=function(result){
//处理返回结果的相关逻辑
}
jQuery将这个过程包装成了简洁API:
jQuery.getJSON("http://example.com/data.json?callback=jsonCallback",function(result){ console.log(result); });
或者
$.ajax({ url:"http://crossdomain.com/services.php", dataType:'jsonp', data:'', jsonp:'callback', success:function(result) { }, timeout:3000 });
jQuery将上面的URL中最后的问号替换为一个由它创建的随机命名的临时函数。服务器会获取这个callback参数,使用这个名字作为回调函数名称返回给客户端。
参考资料:
《基于MVC的Javascript Web富应用开发》
http://msdn.microsoft.com/zh-cn/library/dd573303%28v=vs.85%29.aspx