做 Web 开发经常需要面对跨域问题,跨域问题的根源是浏览器安全中的同源策略,比如说,对于 http://www.a.com/1.html 来说:
- http://www.a.com/2.html 是同源的;
- https://www.a.com/2.html 是不同源的,原因是协议不同;
- http://www.a.com:8080/2.html 是不同源的,原因是端口不同;
- http://sub.a.com/2.html 是不同源的,原因是主机不同。
在浏览器中,<script>、<img>、<iframe> 和<link> 这几个标签是可以加载跨域(非同源)的资源的,并且加载的方式其实相当于一次普通的 GET 请求,唯一不同的是,为了安全起见,浏览器不允许这种方式下对加载到的资源的读写操作,而只能使用标签本身应当具备的能力(比如脚本执行、样式应用等等)。
最常见的跨域问题是 Ajax 跨域访问的问题,默认情况下,跨域的 URL 是无法通过 Ajax 访问的。这里我记录我所了解到的跨域的方法:
1. 服务器端代理,这没有什么可说的,缺点在于,默认情况下接收 Ajax 请求的服务端是无法获取到的客户端的 IP 和 UA 的。
2. iframe,使用 iframe 其实相当于开了一个新的网页,具体跨域的方法大致是,域 A 打开的母页面嵌套一个指向域 B 的 iframe,然后提交数据,完成之后,B 的服务端可以:
- 返回一个 302 重定向响应,把结果重新指回 A 域;
- 在此 iframe 内部再嵌套一个指向 A 域的 iframe。
这两者都最终实现了跨域的调用,这个方法功能上要比下面介绍到的 JSONP 更强,因为跨域完毕之后 DOM 操作和互相之间的 JavaScript 调用都是没有问题的,但是也有一些限制,比如结果要以 URL 参数传递,这就意味着在结果数据量很大的时候需要分割传递,甚是麻烦;还有一个麻烦是 iframe 本身带来的,母页面和 iframe 本身的交互本身就有安全性限制。
3. 利用 script 标签跨域,这个办法也很常见,script 标签是可以加载异域的 JavaScript 并执行的,通过预先设定好的 callback 函数来实现和母页面的交互。它有一个大名,叫做 JSONP 跨域,JSONP 是 JSON with Padding 的略称。它是一个非官方的协议,明明是加载 script,为啥和 JSON 扯上关系呢?原来就是这个 callback 函数,对它的使用有一个典型的方式,就是通过 JSON 来传参,即将 JSON 数据填充进回调函数,这就是 JSONP 的 JSON+Padding 的含义。
在互联网上有很多 JSONP 的服务来提供数据,本质上就是跨域请求,并且在请求 URL 中指定好 callback,比如 callback=result,那么在获取到这些数据以后,就会自动调用 result 函数,并且把这些数据以 JSON 的形式传进去,例如(搜索“football”):
http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=football&callback=result
使用 JQuery 来调用就写成:
$.getJSON("http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=football&callback=?",function(data){ //... });
总的来说,JSONP 的跨域方式的局限性在于,只能使用 GET 请求,并且不能解决不同域的两个页面之间如何进行 JavaScript 调用的问题。
4. Flash 跨域:
它会访问目标网站根目录下面的 crossdomain.xml 文件,根据文件中的内容来确定是否允许此次跨域访问:
<cross-domain-policy> <allow-access-from domain="xxx.xxx.com" /> </cross-domain-policy>
5. img 标签也可以使用,这也是一种非常常见的方法,功能上面弱一点,只能发送一个 get 请求,没有什么回调,Google 的点击计数就是这样确定的。
6. window.PostMessage,这个算是 HTML5 新加入的为跨域通讯考虑的机制,只有 Firefox 3、Safari 4 和 IE8 及之后的版本支持。使用它向其它窗口发送消息的调用方式如下:
otherWindow.postMessage(message, targetOrigin);
在接收的窗口,需要设置一个事件处理函数来接收发过来的消息:
window.addEventListener("message", receiveMessage, false); function receiveMessage(event){ if (event.origin !== "http://example.org:8080") return; }
注意这里必需要使用消息的 origin 和 source 属性来验证发送者的身份,否则会造成 XSS 漏洞。
7. Access Control
有一些浏览器支持 Access-Control-Allow-Origin 这样的响应头,比如:
header("Access-Control-Allow-Origin: http://www.a.com");
就指定了允许对 www.a.com 跨域访问。
8. window.name
这个东西其实以前被用作黑客 XSS 的手段,其本质是,当 window 的 location 变化的时候,页面会重新加载,但是有趣的是,这个 window.name 居然不发生变化,那么就可以用它来传值了。配合 iframe,改变几次 iframe 的 window 对象,就完成了实用的跨域数据传递。
9. document.domain
这个方式适用于 a.example.com 和 b.example.com 这种跨域的通信,因为二者有一个共有的域,叫做 example.com,只要设置 document.domain 为 example.com 就可以了,但是如果 a.example1.com 和 b.example2.com 之间要通信,它就没办法了。
10. Fragment Identitier Messaging(FIM)
这个方法很有意思,也需要 iframe 的配合。Fragment Identitier 就是 URL 的井号(#)后面的经常用于锚点定位的部分,这部分的改变不会导致页面刷新,母窗口可以随便访问 iframe 的 URL,而 iframe 也可以随便访问母窗口的 URL,那这二者之间就可以通过改变 Fragmement Identitier 来实现通信了。缺点是 Fragmement Identitier 的改变会产生不必要的历史记录,而且也有长度限制;另外,有的浏览器不支持 onhashchange 事件。
11. Cross Frame(CF)
这种方法是上述 FIM 方法的变种,CF 和 FIM 的本质其实在我的 《GWT 初体验》这篇文章里面都有介绍(只不过是被用来实现历史和后退功能了),它会动态创建一个不可见的 iframe,指向异域,处理完以后,这个 iframe 的 URL 中的 Fragment Identitier 包含了处理结果,供母页面访问,而浏览器的 URL 没有任何变化。
12. Cookie+P3P 协议
利用 P3P 协议下跨域访问 Cookie 的特性,来实现跨域访问,也算一奇招。P3P 是 W3C 公布的一项隐私保护推荐标准,旨在为网上冲浪的 Internet 用户提供隐私保护。把 Cookie 的 path 设置为“/”,即没有任何域的限制,这个时候有的浏览器下面允许别的 URL 的页面来读取,有的则不允许,这种情况下需要在母页面响应的头上面设置 P3P 的头:
P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"
文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》
总结的很全面~~