跨域技术
在实际开发中,大家会经常遇到跨域问题,这时我们就在根据不同的情况选择不同的跨域方式,我整理了一些目前常用的跨域技术与大家分享。
一、什么是跨域
跨域请求,简单说就是从一个域访问另一个域下的数据或资源。受同源策略限制,JavaScript不能跨域! 这里提到了同源策略,那么什么是同源策略呢?
同源策略(Same Origin Policy),它是由Netscape提出的一个著名的安全策略。 现在所有支持JavaScript 的浏览器都会使用这个策略。同源策略阻止从一个域上加载的脚本去获取或操作另一个域上的文档属性。也就是说,受到请求的 URL 的域必须与当前 Web 页面的域相同。这说明浏览器隔离来自不同源的内容,以防止它们之间的操作。
为何要使用同源策略? 答案:安全! 一个简单的例子:比如一个黑客,他利用Iframe把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时,他的页面就可以通过Javascript读取到你的表单中input中的内容,这样用户名,密码就轻松到手了。后果会非常严重!
那么什么才是同源?
所谓同源是指域名,协议,端口均相同。
同源的几种情况:
属于跨域,sub.a.com 和 sub1.a.com 之间也是跨域。
二、为什么要跨域
我们实际上做项目的时候,不可避免地会根据项目需求进行跨站访问,子域和主域之间数据共享等,受到同源策略的影响,要满足这些需求,就要用跨域技术来实现。
三、跨域技术
常用的跨域方式有:
• window.postMessage
• window.name
• JSONP
• Server-Proxy
• document.domain
• FIM
• Flash
window.postMessage
window.postMessage是HTML5的一个新特性,是一个安全的实现直接跨域通信的方法。但是目前并不是所有浏览器都能支持。IE8, IE9 ,Firefox, Opera, Chrome和Safari都支持postMessage。
特点:使用简单,安全,不兼容ie6,ie7。window.postMessage(message, targetOrigin) //接收二个参数,第一个参数是要发送的数据,第二个参数是非常重要,主要是出于安全的考虑,一般填写允许通信的域名。 //receiveMessage是一个自定义的监听函数,它接收一个参数: function receiveMessage(e) { if(e.origin == ‘http://www.a.com’) { alert(e.data); //msg } }
window.name window对象的name是个很特别的属性,当该window的location变化重新加载后,它的name属性值依然保持不变,获取window.name需要在同域下进行。
特点:安全,不会产生历史记录,传递数据受限制,最大为2MB。原理:在a域中创建一个iframe,并设置window.name=跨域数据,然后改变iframe的src指向b域中的xd.html,此时在b域中在window.onload时即可通过获取window.name值来得到传递过来的数据。这种方法只适合于单向的数据传递,不支持双向跨域。采用window.name如何实现双向跨域?
方案一:在本域放置跨域文件xd.html如上图所示,需要在a域中放置跨域文件xd.html,当数据从a传到b后,b域中获取到a域传过来的window.name,进行数据交互后欲将数据再传回到a域,此时就需要在b域中再创建一个iframe,设置iframe的window.name,再将其src导航回至a域中,此时a域中的xd.html即可获取到从b域传过来的window.name,再通过window.parent.parent调用同域内的原始页面的js方法实现数据的回调,执行完成后即刻将xd iframe移除。这样就完成一次双向的数据传递。下次请求再次走这个流程完成跨域。
特点:简单,安全,但第三方需要部署跨域文件,依赖性较强。为了解决第三方部署更方便,更简单,我们需要去掉这个跨域文件,根据window.name的特性:需要在同域下读取。我们于是有了下面的方案:方案二:创建本域的历史记录原理:首先为了避免污染第三方页面中的window.name,我们首先在a域中创建一个空白的iframe(我们所有的操作全封装在此),在该iframe中再进行创建一个供跨域传输使用的xd iframe,之后的第一件事在这个xd iframe中创建一个本域内的历史记录,本方案中最关键的地方就在此,之后的单向传递数据同上述方案,当b域中拿到a域的数据并进行处理后,如何再将这些数据再传回a域呢。如图中所示,只需设置window.name,再调用history.back()方法即可(由于事先先创建了一个a域中的历史记录),此时a域中的xd iframe就回到了最初,也就是和a同域,此时即可通过监听xd iframe的onload事件获取window.name了,这就完成了双向跨域。注:Create History
特点:不需要部署跨域文件,有多个请求时需要排队处理,会产生历史记录,每次都会重新请求xd.html。_
var doc = XDIFRAME.contentWindow.document;
doc.write(‘<html><head></head><body></body></html>’);
doc.close();上面的方案可以实现双向跨域,但由于会产生历史记录,不是很好的解决方法,我们看下面的方案。
方案三:双方轮询window.name该方案依然是需要创建一个空白的iframe,从a到b的数据传递同方案一,在b域进行处理后的数据如何传回至a域?如图中所示,只需调用parent.window.name=some data即可,而在a域中需要事先开启轮询window.name方式来监听传递回来的数据,如果window.name发生变化,即视为一次合理的请求,将数据传给回调函数进行处理。
特点:不需要部署跨域文件,但可能由于轮询而存在资源消耗(需做相应处理),仅在IE6,7下有效。
- JSONP(JSON WITH PADDING)
**jsonp是一个简单高效的跨域方式,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问。_特点:不受同源策略的限制,兼容性更好,易于实现,只支持get。
- Server-Proxy(服务器端代理) 在服务器端设置代理文件,用来进行跨域请求,客户端直接发送本域内的Ajax请求,从而达到跨域请求的目的。特点:在客户端不存在跨域请求。需要增加代理文件,为了安全需要和对方约定。
- document.domain 通过修改document的domain属性,可以在主域和子域或者不同的子域之间通信。如:www.a.com, sub1.a.com, sub2.a.com可以通过设置document.domain=‘a.com’来实现通信。特点:便于主域、子域之间的通信,使用方便,但不支持与其他域的通信,如www.b.com。
- FIM(Fragment Identifier Messaging)不同的域之间,JavaScript只能做很有限的访问和操作,父窗口可以对iframe进行URL读写,iframe也可以读写父窗口的URL,于是我们可以用url的hash(#后面的数据)来进行传递数据,用轮询来监听获取的数据。特点:实现方便,兼容性强,受url长度限制,改变hash会产生历史记录,而且用到长轮询。
- Flash
Flash有 自己的一套安全策略,服务器可以通过crossdomain.xml文件来声明能被哪些域的SWF文件访问,SWF也可以通过API来确定自身能被哪些域 的SWF加载。当跨域访问资源时,例如从域www.a.com请求域www.b.com上的数据,我们可以借助Flash来发送HTTP请求。首先,修改www.b.com上的crossdomain.xml(一般存放在根目录,如果没有需要手动创建) ,把www.a.com加入到白名单。其次,通过Flash URLLoader发送HTTP请求,最后,通过Flash API把响应结果传递给JavaScript。特点:支持get、post,但需要部署flash文件及crossdomain.xml。
四、总结
跨域的方法很多,不同的应用场景我们都可以找到一个最合适的解决方案。比如单向的数据请求,我们应该优先选择JSONP,双向通信我们可以采取多种跨域方式组合的方式来实现,比如为了兼容各种浏览器我们采用postMessage和window.name等。当然还有其他跨域方式,希望大家共同探讨分享。
参考资料:
postMessage:http://dev.w3.org/html5/postmsg/#dom-window-postmessage
跨域资源共享的10种方式: http://www.woiweb.net/10-cross-domain-methods.html
CrossDomain:http://code.google.com/p/j-et/wiki/CrossDomain
https://developer.mozilla.org/En/Same_origin_policy_for_JavaScript
http://www.slideshare.net/mehmetakin/ajax-world
http://www.slideshare.net/SlexAxton/breaking-the-cross-domain-barrier