跨域通信
在工作的一段时间里,给我最大的感触就是:公司有非常多的子系统,而且子系统与子系统之间交互非常的频繁,虽然公司拥有封装了HttpClient及增加了路由功能的R系统(该系统目的就是满足现下系统与系统之间通信频繁的问题),但是大家都知道HttpClient只限于服务器端使用,当我们想用客户端脚本,比如Javascript来调用其他系统(不同域)的接口时,就会出现跨域通信的问题。那么为什么会出现跨域通信问题呢?当遇到跨域通信问题时,我们又有几种解决方案呢?另外,跨域通信又会引发哪些潜在的问题呢?下面我将慢慢解释这几个问题。
一、产生跨域通信问题的本质原因
"无规矩不成方圆",这句话大家都知道,当我们在互联网的浪潮中冲浪的时候,我们无需考虑任何安全问题。因为互联网为了网民的安全,定制了很多策略(规矩),有一种策略叫同源策略,是每一种浏览器都必须实现并遵守的!那么同源策略到底限制了什么呢?
同源策略规定:不同域的客户端脚本在没有明确授权的情况下,不能读写对方的资源(好比你不能碰别人的女朋友一样),这里有几个关键字很有必要解释一下。
1、域、子域、同域
所谓同域(源)指:当A域与B域具有相同的协议、域名、端口时称为同域(源)
2、客户端脚本:由浏览器解释和执行的脚本语言,比如现下流行的Javascript,ActionScript,之前流行的VBScript
3、授权:服务端可以对客户端的访问进行授权,那么客户端也可以授权,比如HTML5中新标准中,当目标站点返回HTTP响应头Access-Control-Allow-Origin:http://www.cnblogs.com(A),那么浏览器就允许A站点去访问目标站点。
4、读写权限:Web上的资源很多,比如HTTP请求头的Referer只读,cookie即可写亦可读
5、资源:资源是个广泛的概念,只要是数据都可以认为是资源。同源策略里的资源特指Web客户端的资源(HTTP头,DOM树..)
在这里特别要注意的是,同源策略虽然限制了客户端的资源,但对静态的资源文件是没有加以限制的,例如客户端脚本文件,样式CSS文件、图片,flash资源等静态文件。(<script src=""><img src=""> <link rel=""><iframe src="">),但如果A域的js,想修改B域的dom元素也会造成跨域问题(2014.4.28补充)
二、为什么需要同源策略限制
我们举例说明:比如一个黑客程序,他利用iframe把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时,他的页面就可以通过Javascript读取到你的表单中input中的内容,这样用户名,密码就轻松到手了。如果没有该策略的限制,后果可想而知。
三、同源策略的应用场景
前面介绍了同源策略的规定其存在的价值,那么浏览器的同源策略究竟在哪些场景会起作用呢?下面的三种场景是比较常见的!
1、窗口与窗口(不同域)之间的交互
2、iframe嵌入了不同域的资源(浏览器并不限制iframe嵌入不同域的资源,但限制客户端脚本去访问)
3、AJax应用(异步),请求不同域的资源
四、绕过同源策略解决跨域通信的方式
互联网的发展催生了跨域通信的需求,各种跨域方法和协议满足了需求但也增加了各种风险(风险下面会讲)。尤其是现在mashup的盛行。那么怎么去绕过浏览器的同源策略呢?下面我会讲在实际应用中比较常用的绕过同源策略的方式,这些方式会分为:客户端技术和服务端技术。但这两种方式的本质就是利用同源策略的一个漏洞(同源策略虽然限制了客户端的资源,但对静态的资源文件是没有加以限制的)和浏览器中不能直接来跨域访问,而在服务器端没有跨域安全限制(可以在服务端完成跨域访问,而在客户端来取得结果)。客户端技术利用的是前者,而服务端技术利用的是后者。
客户端技术
- iframe + document.domain(仅适用于子域之间,例如bbs.pclady.com.cn和www.pclady.com.cn)
- iframe(其原理:在iframe加载新页面时,window.name的值是保持不变的,由此可以重定向iframe的引用地址,由外域转到本域) -> JQuery的window.name插件(封装了iframe的实现) -- 支持post请求
- 动态Script标签->Jsonp -> JQuery的$.getScript(弊端只支持get请求,没有差错控制机制)
- HTML5 postMessage(需要现代的浏览器才兼容,像之前的ie6不兼容)
- Flash + crossdomain跨域(需要用户安装flash,在互联网应用里用户体验不好)
1、动态Script标签,返回的数据格式必须是text/javascript(并非一定是JSON,可以是任何数据类型),跨域通信盛行之后,慢慢就产生了JSONP的概念(就是将动态插入Script标签的技术美称为JSONP,但网上却有一大堆关于JSONP的解释,简直是误导众生啊!)
1 <html> 2 <head> 3 <title>How Many Pictures Of Madonna Do We Have?</title> 4 <script type="text/javascript"> 5 // 回调函数 6 function ws_results(obj) 7 { 8 // obj的格式可以是任意数据类型,比如JSON,字符串,xml 9 // 我们只需要相应的解析即可 10 // 服务器端的返回格式一定得是这样 ws_results(obj); 11 alert(obj.ResultSet.totalResultsAvailable); 12 } 13 14 function onClick() 15 { 16 var script = document.createElement("script"); 17 script.type = "text/javascript"; 18 script.src = "http://search.yahooapis.com/ImageSearchService/V1/imageSearch?appid=YahooDemo&query=Madonna&output=json&callback=ws_results"; 19 document.body.appendChild(script); 20 } 21 </script> 22 </head> 23 <body> 24 <input type="button" value="click me!" onclick="onClick()"> 25 </body> 26 </html>
2、Iframe+document.domain
1 /* 2 对于主域相同而子域不同的例子,可以通过设置document.domain的办法来解决。具体的做法是可以在http://www.a.com/a.html和http://script.a.com/b.html两个文件中分别加上document.domain = ‘a.com’;然后通过a.html文件中创建一个iframe,去控制iframe的contentDocument,这样两个js文件之间就可以“交互”了。当然这种办法只能解决主域相同而二级域名不同的情况,如果你异想天开的把script.a.com的domian设为alibaba.com那显然是会报错地! 3 */代码如下: 4 www.a.com上的a.html 5 document.domain = 'a.com'; 6 var ifr = document.createElement('iframe'); 7 ifr.src = 'http://script.a.com/b.html'; 8 ifr.style.display = 'none'; 9 document.body.appendChild(ifr); 10 ifr.onload = function(){ 11 var doc = ifr.contentDocument || ifr.contentWindow.document; 12 // 在这里操纵b.html 13 alert(doc.getElementsByTagName("h1")[0].childNodes[0].nodeValue); 14 }; 15 16 script.a.com上的b.html 17 document.domain = 'a.com'; 18 /* 19 这种方式适用于{www.kuqin.com, kuqin.com, script.kuqin.com, css.kuqin.com}中的任何页面相互通信。 20 备注:某一页面的domain默认等于window.location.hostname。主域名是不带www的域名,例如a.com,主域名前面带前缀的通常都为二级域名或多级域名,例如www.a.com其实是二级域名。 domain只能设置为主域名,不可以在b.a.com中将domain设置为c.a.com。 21 这种方式的缺陷: 22 1、安全性,当一个站点(b.a.com)被攻击后,另一个站点(c.a.com)会引起安全漏洞。 23 2、如果一个页面中引入多个iframe,要想能够操作所有iframe,必须都得设置相同domain。 24 */
3、Iframe->window.name 详情可查看http://www.cnblogs.com/rainman/archive/2011/02/21/1960044.html
1 <html> 2 <head> 3 <title>ddd</title> 4 </head> 5 <body> 6 <script type="text/javascript"> 7 function check() 8 { 9 var btn = document.getElementById("test_submit"); 10 var frm = document.forms["test_form"]; 11 var ifm = document.getElementById("test_iframe"); 12 frm.action = "http://xxx.xxx.xxx/post.php"; 13 frm.target = "test_iframe"; 14 frm.submit(); 15 btn.disabled = "disabled"; 16 ifm.onload = function(){ 17 btn.disabled = ""; 18 var str = ifm.contentWindow; 19 alert(str.document.body.innerHTML); 20 ifm.src = "about:blank"; 21 ifm.onload = null; 22 } 23 return false; 24 25 } 26 27 </script> 28 <form id="test_form" name="test_form" > 29 <input type="hidden" name="content" value="xxx" /> 30 <input type="submit" name="test_submit" id="test_submit" /> 31 </form> 32 <iframe id="test_iframe" name="test_iframe" width="1" height="1" style="display:none"></iframe> 33 </body> 34 </html> 35 36 Iframe实现post跨域
4、5由于用得不多,在这里就不详细解释了!详细可查看http://book.51cto.com/art/200903/113178.htm
服务端技术
- 正向代理:与浏览器原理一样
- 反向代理:利用apache server或者nginx等Http服务器进行对url进行重写
- HttpClient: 模拟Http请求(通过HttpClient对其它域发出HTTP请求,只要不通过浏览器就不会有同源策略的限制)
在那么多处理方式中,当然每种方式都有它们的优缺点,当我们去选择这些方式的时候,我们应该以下面几个方面去考虑。
1、自己是否可以操作其它域的服务端的资源
2、请求的方式是get还是post
3、跨域通信的方式(跨域通信有2种:本域和子域通信和本域和其它域通信)
五、绕过同源策略引发的问题
在避免同源策略时会向恶意用户露出攻击面,当恶意代码被插入 Web 应用程序中时当前的应用程序也易于受到攻击。遗憾的是,恶意代码进入 Web 应用程序的方法多种多样。使我们防不胜防。两种比较常见的的XSS(反射式与存储式)和CSRF,有兴趣的可以具体去了解一下!
六、其它同源策略
Cookie 同源策略:Cookie中的同源只关注域名,忽略协议和端口。所以https://localhost:8080/和http://localhost:8081/的Cookie是共享的。