如今cross-origin resource sharing(跨域资源共享,下简称CORS)已经十分普及,算上IE8的不标准兼容(XDomainRequest),各大浏览器基本都已支持,当年为了先后端分离、iframe交互和第三方插件开发而头疼跨域是时代已通过去,但当年为了跨域无所不用其极的风骚操做却依然值得学习。
本篇文章不是从实用的角度考量这些旧时代的跨域手段,而是更偏向理论的阐述,并引起对浏览器安全的思考,由于跨域实际上也是各种攻击的核心。
本人我的能力有限,欢迎大牛一块儿讨论,批评指正。javascript
1995年,同源政策由Netscape公司引入浏览器。目前,全部浏览器都实行这个安全策略。
核心是确保不一样源提供的文件(资源)之间是相互独立的。换句话说,只有当不一样的文件脚本是由相同的域、端口、HTTP协议提供时,才没有特殊的限制。特殊限制能够细分为两个方面:html
一个表格看懂什么是同源?前端
origin(URL) | result | reason |
---|---|---|
http://example.com |
success | 协议,域名和端口号80均相同 |
http://example.com:8080 |
fail | 端口不一样 |
https://example.com |
fail | 协议不一样 |
http://sub.example.com |
fail | 域名不一样 |
至于为何说这是个安全策略?
这个就要提到cookie-session机制,众所周知HTTP是无状态协议,而服务器如何知晓用户的登陆状态?传统上是使用了cookie-session这一机制,也就是服务器为每一个访问者生成了一个session标识,而session标识会被服务器包含在应答头中返回,浏览器解析到应答头中的set-cookie就把这串session标识保存到本地cookie中,利用cookie每次请求同一个域都会带上的特性,服务器器就能知晓当前的用户登陆状态。
因此若是让浏览器向不一样源发起请求,就会形成很大的危险。好比用户登陆了银行的网站A,也就是说A站已经在浏览器留下了cookie,这时候用户又访问了B站,若是能在B站页面上发起A站的请求,就至关于B站能够冒充用户,在A站随心所欲。
因而可知,"同源策略"是必需的,不然cookie能够共享,互联网就毫无安全可言了。java
同源策略提出的时代仍是传统MVC架构(jsp,asp)盛行的年代,那时候的页面靠服务器渲染完成了大部分填充,内容也比较简单,开发者也不会维护独立的API工程,因此其实跨域的需求是比较少的。
新时代先后端的分离和第三方JSSDK的兴起,咱们才开始发现这个策略虽然大大提升了浏览器的安全性,但有时很不方便,合理的用途也受到影响。好比:jquery
因而乎,在没有标准规范的时代,如何解决这些问题的跨域方案就被纷纷提出,可谓百家争鸣,其中不乏使人惊叹的骚操做,这样的极客精神依然值得咱们敬佩和学习。ajax
JSON-P是各种跨域方案中流行度较高的一个,如今在某些要兼容旧浏览器的环境下还会被使用,著名的jQuery也封装其方法。请勿见名知义,名字中的P是padding“带填充”的意思,这个方法在通讯过程当中使用的并非普通的json,而是自带填充功能的JavaScript脚本。
如何理解“自带填充功能的JavaScript脚本”,看看下面的例子或许比较简单,若是一个js文件里这样写并被引入,则全局下就会有data对象,也就是说利用js脚本的引入和解析能够用来传递数据,若是把js脚本换成函数运行命令岂不是能够调用全局函数了。这就是JSON-P方法的核心思想,它填充的是全局函数的数据。json
var data = { a: 1, b: 2 }
【PS】
<script>
标签不受同源策略限制,但只能发起get请求。
原理及流程后端
<script>
标签,将标签插入页面浏览器便会发起get请求;// 定义回调函数 function getTheAnimal(data){ var myAnimal = data.animal; } // 新建标签 var script = document.createElement("script"); script.type = "text/javascript"; // 经常使用的在url参数部分跟服务器约定号回调函数名 script.src = "http://demo.com/animal.json?callback=getTheAnimal"; document.getElementByTagName('head')[0].appendChild(script);
总结 api
优势:跨域
缺点:
这个方法其实是利用浏览器容许iframe内的页面只要是跟父页面是同个一级域名下,就能被父页面修改和调用的特色。也许你会疑问,上面讲同源策略的表格中很明确二级域名不一样也是算不一样源,这岂不矛盾了?
这其实不矛盾,若是正常操做确实会被同源策略限制,但浏览器的document.domain
容许网站将主机部分更改成原始值的后缀。这意味着,寄放在sub.example.com的页面能够将它的源设置为example.com,可是并不能将其设置为alt.example.com或google.com。
【PS】这里有一个细节,父子页面均要设置document.domain
才能被互相访问,单一一个是没法跨域的。document.domain
的特色:只能设置一次;只能更改域名部分,不能修改端口号和协议;重置源的端口为协议默认端口。
原理及流程
document.domain = 'demo.com'
,并能够包含发起ajax的工具;document.domain = 'demo.com'
;// 最简单的代理文件proxy.html <!DOCTYPE html> <html> <script> document.domain = 'demo.com'; </script> <script src="jquery.min.js"></script> </html>
// 新建iframe var iframe = document.createElement('iframe'); // 连接到代理页 iframe.src = 'http://api.demo.com/proxy.html'; // 代理页就绪时触发 iframe.onload = function(){ // 因为代理页已经和父页设置了相同的源,父的脚本能够调用代理页的ajax工具; // 因为是在子页面发起,其请求地址就跟子页面同源了。 iframe.contentWindow.jQuery.ajax({ method: 'POST', url: 'http://api.demo.com/products', data: { product: id, }, success: function(){ document.body.removeChild(iframe); /*...*/ } }) } document.getElementsByTagName('head')[0].appendChild(iframe);
总结
优势:
缺点:
form表单的target属性能够指定一个iframe,使主页面不跳转,而iframe内跳转,因此这个方法的核心就是利用表单提交,并在iframe中获取数据。
要访问iframe内外页面互访也是必须设置同源,这点与子域代理是类似的;而iframe内回调父页面,又与JSON-P类似,能够说是两个思路的合体版。
form表单提交后返回的是页面,因此与JSON-P不一样的是,返回的是包含了自带填充功能的JavaScript脚本的页面,提及来有点绕,简单来讲就是把JSON-P返回的脚本放到一个html页面里自运行。
相比子域代理的方法,它不须要代理页。
【PS】form表单提交的特色就是会致使整个页面跳转,返回数据是在新的页面上,这样天然不会产生跨域的问题。
原理及流程
document.domain = 'demo.com'
;// 新建并隐藏iframe var frame = document.createElement('iframe'); iframe.name = 'post-review'; frame.style.display = 'none'; // 新建表单 var form = document.createElement('form'); form.action = 'http://api.demo.com/products'; form.method = 'POST'; form.target = 'post-review'; // 添加数据 var score = document.createElement('input'); score.name = 'score'; score.value = '5'; // 添加数据 var message = document.createElement('input'); message.name = 'message'; message.value = 'hello world'; // 把数据加到表单 form.appendChild(score); form.appendChild(message); // 渲染iframe和表单 document.body.appendChild(frame); document.body.appendChild(form); // 提交表单发起请求 form.submit(); // 完成清理元素 document.body.removeChild(form); document.body.removeChild(frame);
// 最简单返回html <!DOCTYPE html> <html> <script> document.domain = 'demo.com'; window.parent.jsonpCallback('{"status":"success"}'); </script> </html>
总结
因为这个方法是JSON-P与子域名代理的结合版,能够说即拥有二者的优势,也保留了二者一些缺点。
优势:
缺点:
这方法利用了window.name
的特性:一旦被赋值后,当窗口被重定向到一个新的URL时不会改变它的值。这一行为使得不一样域的特定文档能够读取该属性值,所以能够绕过同源策略并使跨域消息通讯成为可能。
【PS】例子里演示的是发起get请求,只要把请求地址直接写到src里就好了。若是想要发起其余类型的请求,能够类比采用模拟的form的方式进行改造。
原理及流程
// 新建iframe var iframe = document.createElement('iframe'); var body = document.getElementByTagName('body'); // 隐藏iframe并连接地址 iframe.style.display = 'none'; iframe.src = 'http://api.demo.com/server.html?id=1'; // 由于须要两次跳转,这里有个完成标记 var done = fasle; // 这里会触发至少两次,一次因为非同源是取不到值的。 iframe.onload = iframe.onreadystatechange = function(){ if(! this.readyState && (iframe.readyState !== 'complete' || done)){ return; } console.log('Listening'); var name = iframe.contentWindow.name; if(name){ console.log(iframe.contentWindow.name); done = true; } }; body.appendChild(iframe);
// 最简单返回html <!DOCTYPE html> <html> <script> function init(){ window.name = 'hello'; window.location = 'http://demo.com/empty.html' } </script> <body onload="init();"></body> </html>
总结
优势:
缺点:
这个方法利用了location的特性:不一样域的页面,能够写不可读。而只改变哈希部分(井号后面)不会致使页面跳转。也就是可让父、子页面互相写对方的location的哈希部分,进行通信。
原理及流程
【PS】父页面会循环检查哈希是否改变来读取值,由于这种降级方案的使用环境通常是不会有hashchange事件的。演示里是最简单的get方法,若是想要发起其余类型的请求,能够类比采用模拟的form的方式进行改造,但记住不要丢失父页面的url。
// 获取当前url var url = window.location.href; // 新建iframe var iframe = document.createElement('iframe'); // 隐藏iframe并设置连接,把当前url带上 iframe.style.display = 'none'; iframe.src = 'http://api.demo.com/server.html?id=1&url=' + encodeURIComponent(url); var body = document.getElementByTagName('body')[0]; body.appendChild(iframe); // 循环监听处理 var listener = function(){ // 读取 var hash = location.hash; // 还原 if(hash && hash !== '#'){ console.log(hash.replace('#', '')); window.loacation.href = url + '#'; } // 继续监听 setTimeout(listener, 100); }; listener();
// 最简单返回html <!DOCTYPE html> <html> <script> function init(){ // 剪裁出父页面的url var parentUrl = ''; var url = window.location.href; var str = url.split('?')[1].replace('?', ''); strs = str.split("&"); for(var i = 0; i < strs.length; i ++) { if(strs.split("=")[0] === 'url'){ parentUrl = strs.split("=")[1]; } } // 设置到父页面上 window.parent.location = decodeURIComponent(parentUrl) + '#helloworld'; } </script> <body onload="init();"></body> </html>
总结
优势:
缺点:
W3C的标准化跨域方案,让现代浏览器跨域已经不是什么复杂的事。这部分网上资料已经不少,这里就只是简单介绍。
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
它容许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
H5的window.postMessage为浏览器带来了一个安全的。基于事件的消息api。
只要是window对象,基本均可以使用这个方法,也就是说window.name、window.hash这类风骚的操做都已成为降级方案。
上述的各种非标准的骚操做,都算是对同源策略的破解办法,在方便开发者完成跨域目的的同时,各种恶意的攻击者也天然会利用这些方案为非做歹。 其中子域名代理的风险最低,由于须要服务器设置特定的子域名,也就是已是两个源的协商结果,通常黑客是难以模拟的。 风险最高的要算JSON-P的方案,由于这是任何客户端均可随意使用的办法,CSRF攻击的核心也是利用了特定标签的跨域性发起请求,因此JSON-P最好用在无用户状态的低安全性API上。