当前位置: 首页 > Web前端 > HTML

[骄傲]当年那些风骚的跨域操作

时间:2023-04-02 13:56:54 HTML

前言现在跨域资源共享(Cross-originresourcesharing,以下简称CORS)已经很流行了。算上IE8的非标准兼容(XDomainRequest),各大浏览器基本都支持。前后端分离、iframe交互、第三方插件开发的跨域头痛时代已经过去,但跨域使用的风骚操作还是值得学习的。本文并没有从实用的角度去考虑这些老掉牙的跨域方法,更多的是理论性的,并引发了对浏览器安全的思考,因为跨域其实是各种攻击的核心。个人能力有限,欢迎大牛讨论,批评指正。同源策略1995年,Netscape向浏览器引入了同源策略。目前,所有浏览器都实现了这个安全策略。核心是保证不同来源提供的文件(资源)相互独立。换句话说,只要不同的文件脚本由相同的域、端口、HTTP协议提供服务,就没有特殊限制。特殊限制可以细分为两个方面:对象访问限制:主要体现在iframe中,如果父子页面的来源不同,则不能访问对方的DOM方法和属性(包括Cookie、LocalStorage和IndexDB等).).异常是从不同的来源抛出的。网络访问限制:主要体现在AJAX请求上。如果发起请求的目标来源与当前页面不同,浏览器会限制发起跨站请求,或者拦截返回的请求。一张表就明白什么是同源性?origin(URL)resultreasonhttp://example.com成功协议,域名和端口号80相同http://example.com:8080fail端口不同https://example.comfail协议不同http://sub。example.comfail域名不同至于为什么这是一个安全策略?这是关于cookie-session机制的。关于cookie-session机制的详细解释可以参考我的另一篇文章(传送门)。如果让浏览器向不同的来源发出请求,将会造成很大的危险。例如,如果用户登录银行网站A,即A站在浏览器中留下了cookie。此时用户再次访问B站。如果能在B站页面发起对A站的请求,就相当于B站,你可以冒充用户在A站为所欲为。可见“同源策略”是必须的,否则cookies可以共享,互联网就没有安全可言了。提出跨域解决方案同源策略的时代,还是传统MVC架构(jsp、asp)盛行的时代。当时大部分页面都是服务器渲染填充,内容比较简单,开发者不会维护独立的API项目,所以其实跨域的需求是比较小的。随着新时代前后端分离和第三方JSSDK的兴起,我们开始发现这种策略虽然大大提高了浏览器的安全性,但有时也很不方便,合理使用也受到影响。例如:独立API项目部署使用独立域名,方便管理;前端开发人员需要使用远程API进行本地调试;第三方开发的JSSDK需要嵌入到别人的页面中使用;在公共平台上开放API。于是乎,在一个没有标准、没有规范的时代,关于如何解决这些问题的跨域方案相继提出。可谓是百家争鸣,其中不乏奇葩操作。这样的极客精神,还是值得我们敬佩和学习的。JSON-PJSON-P是目前最流行的跨域解决方案之一,在一些需要兼容旧版浏览器的环境中仍在使用。大名鼎鼎的jQuery也封装了它的方法。请不要被名字所迷惑。名称中的P表示填充“带填充”。该方法在通信过程中并没有使用普通的json,而是“带有填充功能的JavaScript脚本”。如何理解“带填充功能的JavaScript脚本”,看下面的例子可能会比较简单。如果是写在js文件中导入,那么在全局环境中就会有一个数据对象,也就是说,使用js脚本的引入和Parsing就可以传递数据了。如果把js脚本换成运行命令的函数,那调用全局函数不就可以了吗。这是JSON-P方法的核心思想,用全局函数的数据填充。vardata={a:1,b:2}【PS】不仅仅是标签,将标签插入到页面浏览器中,并发起get请求;服务器根据请求返回js脚本,调用回调函数。//定义回调函数functiongetTheAnimal(data){varmyAnimal=data.animal;}//创建一个新标签varscript=document.createElement("script");script.type="text/javascript";//常用在url参数部分后面是服务器的合约号。回调函数名script.src="http://demo.com/animal.json?callback=getTheAnimal";document.getElementByTagName('head')[0].appendChild(script);总结优点:简单,现成-制作工具库(jQuery)支持;支持古老的浏览器(IE8-)。缺点:只能是GET方法;受浏览器URL中最大长度2083个字符的限制;无法调试,无法检测服务器错误的具体原因;存在CSRF安全风险;只能异步,不能同步阻塞;基于REST的API规范。子域名代理的方式其实就是利用了浏览器允许iframe中的页面只要和父页面在同一个一级域名下就可以被父页面修改调用的特性。你可能会疑惑,上面的同源策略表说的很清楚,不同的二级域名也算异源。这不是自相矛盾吗?这其实并不矛盾,如果正常运行确实受同源策略限制,但是浏览器的document.domain允许网站将host部分改成原值的后缀。这意味着托管在sub.example.com上的页面可以将其来源设置为example.com,但不能设置为alt.example.com或google.com。【PS】这里有一个细节。父子页面都必须设置document.domain才能相互访问。单个页面不能跨域。document.domain的特点:只能设置一次;只能修改域名部分,不能修改端口号和协议;复位源的端口是协议的默认端口。原理及流程新建子域,如api.demo.com(页面在主域名demo.com下);子域下需要一个代理文件proxy.html,设置其document.domain='demo.com',即可包含发起ajax的工具;所有API地址都在api.demo.com;在需要发送请求的主域页面设置document.domain='demo.com';创建一个新的iframe标记以链接到代理页面;当iframe的子页面准备就绪时,父页面就可以使用子页面进行ajax请求了。//最简单的代理文件proxy.html//Newiframevariframe=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);优点总结:可以发送任何类型的请求;可以使用基于REST的API规范。缺点:不适合第三方API,给二方使用比较麻烦;iframe对浏览器性能影响较大;不能使用具有非协议默认端口的API。模拟form表单form的target属性可以指定一个iframe,这样主页面不跳转,而是在iframe内部跳转,所以这个方法的核心是利用form提交获取iframe中的数据。访问iframe的内外部页面也需要设置同源,类似于子域代理;而iframe中父页面的回调类似于JSON-P,可以说是两种思路的结合版本。form表单提交后返回的是页面,所以它和JSON-P的区别在于它返回的是一个包含JavaScript脚本的页面,该脚本自带填充功能。转到一个html页面并自己运行它。与子域名代理方式相比,不需要代理页面。【PS】form表单提交的特点是会导致整个页面跳转,返回的数据是在一个新的页面上,自然不会出现跨域问题。原理及流程新建子域,如api.demo.com(页面在主域名demo.com下);所有API地址都在api.demo.com;设置需要向其document.domain='demo.com'发送请求的主域页面;先在父页面定义回调函数;创建一个新的iframe标签并指定名称;新建一个form表单标签,指定target为刚才的iframe,添加数据;提交表单,在iframe内部跳转,自己运行脚本调用父页面的回调函数。//创建并隐藏iframevarframe=document.createElement('iframe');iframe.name='post-review';frame.style.display='none';//创建新表单varform=document.createElement('form');form.action='http://api.demo.com/products';form.method='POST';form.target='post-review';//添加数据varscore=document.createElement('input');score.name='score';score.value='5';//添加数据varmessage=document.createElement('input');message.name='message';message.value='helloworld';//向表单添加数据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总结由于该方法是JSON-P和subdomainproxy可以说兼有两者的优点,也保留了两者的一些缺点。优点:可以发送任何类型的请求;不需要代理页面;支持旧版浏览器(IE8-)。缺点:不太适合第三方API,二方使用比较麻烦;iframe对浏览器性能影响较大;不能使用非协议默认端口的API;需要特殊的接口支持,不能使用基于REST的API规范。window.name方法利用了window.name的属性:一旦分配,当窗口被重定向到一个新的URL时它不会改变它的值。这种行为使得属性值可以被来自不同域的特定文档读取,从而绕过同源策略并实现跨域消息通信。【PS】例子中的演示是发起一个get请求,直接将请求地址写入src即可。如果想发起其他类型的请求,可以使用模拟表单的方式进行改造。原理及流程创建iframe,使用iframe访问非同源地址(发送请求);页面加载时,iframe中的脚本给window.name属性赋值,父页面仍然无法读取子页面的属性(因为来源不同);iframe本身回调到同一个源地址(可能只是一个空白页),此时window.name没有改变;父页面成功读取window.name的值。//新建一个iframevariframe=document.createElement('iframe');varbody=document.getElementByTagName('body');//隐藏iframe和链接地址iframe.style.display='none';iframe.src='http://api.demo.com/server.html?id=1';//因为需要两次跳转,所以这里有一个完成标记vardone=fasle;//这里至少会触发两次,曾经因为不同的Source不可用。iframe.onload=iframe.onreadystatechange=function(){if(!this.readyState&&(iframe.readyState!=='complete'||done)){返回;}console.log('监听');varname=iframe.contentWindow.name;如果(名称){console.log(iframe.contentWindow.name);完成=真;}};body.appendChild(iframe);//最简单的返回html总结优点:任何类型的请求都可以发送;无需设置子域名。缺点:iframe对浏览器性能影响较大;需要特殊的接口支持,不能使用基于REST的API规范;每次要获取一条新消息,都要发起两次网络请求,网络成本高;需要准备空白页,访问是没有意义的,影响流量统计。window.hash方法利用了位置特性:不同域中的页面可以写入但不能读取。并且只改变哈希部分(井号后)不会导致页面跳转。也就是说,父子页面之间可以写入彼此所在位置的hash部分,从而实现相互通信。原理及流程新建一个iframe,使用iframe访问一个非同源地址(发送请求),参数中带上父页面url;页面加载时,iframe中的脚本设置父页面url,并在hash部分带入数据;父页面脚本循环检查hash值的变化,如果有则取值并清除hash值;[PS]父页面会循环检查hash是否发生变化来读取值,因为使用这种降级方案的环境一般不会有hashchange事件。demo是最简单的get方法。如果想发起其他类型的请求,可以使用模拟表单的方式进行改造,但切记不要丢父页面的url。//获取当前urlvarurl=window.location.href;//新建一个iframevariframe=document.createElement('iframe');//隐藏iframe并设置链接,将当前url带上iframe.style。display='none';iframe.src='http://api.demo.com/server.html?id=1&url='+encodeURIComponent(url);varbody=document.getElementByTagName('body')[0];body.appendChild(iframe);//循环监听处理varlistener=function(){//读取varhash=location.hash;//恢复if(hash&&hash!=='#'){console.log(hash.replace('#',''));window.loacation.href=url+'#';}//继续监听setTimeout(listener,100);};listener();//最简单的returnhtml总结优点:可以发送任何类型的请求;无需设置子域名缺点:iframe对浏览器性能影响较大;需要特殊的接口支持,不能使用基于REST的API规范;循环检查哈希是性能密集型的;返回的数据受浏览器URL最大长度2083个字符的限制。现代标准W3C的标准化跨域解决方案,让现代浏览器跨域不再是一件复杂的事情。这部分网上已经有很多资料了,这里只是简单介绍一下。CORSCORS是一个W3C标准,全称是“Cross-originresourcesharing”。它允许浏览器向跨域服务器发送XMLHttpRequest请求,从而克服AJAX只能在同源上使用的限制。CORS参考文档跨域资源共享CORS详解PostMessageH5的window.postMessage给浏览器带来了安全性。基于事件的消息传递API。只要是window对象,基本上都可以用这个方法,也就是说window.name、window.hash等风骚操作都成了降级方案。postMessage参考文档SecurityIssues上面提到的各种非标准操作都是破解同源策略的方法。在方便开发者实现跨域目的的同时,各种恶意攻击者自然会利用这些方案作恶。其中,子域名代理的风险最低,因为服务器需要设置具体的子域名,即已经是两个来源协商的结果,一般黑客很难模仿.风险最大的解决方案是JSON-P,因为这是任何客户端都可以随意使用的方法。CSRF攻击的核心是利用特定标签的跨域请求,所以JSON-P最好在低安全API上没有用户状态时使用。