前端跨域的那些事
这一节,我们来讲一讲,前端跨域的那些事,主要分成这样的几部分来讲解,
一、为什么要跨域?
二、常见的几种跨域与使用场景
三、总结
跨域,通常情况下是说在两个不通过的域名下面无法进行正常的通信,或者说是无法获取其他域名下面的数据,这个主要的原因是,浏览器出于安全问题的考虑,采用了同源策略,通过浏览器对JS的限制,防止恶意用户获取非法的数据。比如这样的一个场景,恶意用户仿造一个银行的官网,在用户输入框中嵌套了银行的页面,如果是没有同源策略的限制,那么恶意用户则可以通过这样的一种方法来获取银行用户的卡号和登录密码,这样对于浏览器来说是没有安全性可言的。同时也可以有效的规避了大部分的XSS攻击(XSS攻击原理:通过向用户界面中注入script脚本,然后在脚本中获取用户的token等身份信息,然后将身份信息发送到恶意用户指定的地方,在正常用户还没有推出的时候,也就是token等身份信息还有效的时候,通过这些信息强制登录,将正常用户挤下系统。)
前端的跨域主要有:JSONP跨域、iframe跨域、window.name 跨域、document.domain 跨域、cookie跨域、postMessage跨域 后端的跨域:http配置
这里我们就主要说明一下前端的跨域,后端的跨域方法不做说明:
跨域方式 | 特点 | 局限 |
---|---|---|
JSONP跨域 | 1、JSONP跨域不是一种前端技术,而是程序猿创造的一种跨域方法 2、JSONP跨域,是一种简单的跨域方法,兼容性比较好 |
|
iframe 跨域 | 1、操作简便 2、兼容性好 | 单纯的使用iframe跨域无法获取其他域名下的数据 |
window.name 跨域 | 1、必须与iframe配合使用 2、可以获取其他域名下的数据 | |
document.domain 跨域 | 1、必须保证两个要跨域的对象是有一个关联域名 | 1、只针对两个跨域对象是关联域名 2、如果一个域名被攻击,那么另外一个域名也有安全问题 |
cookie跨域 | 1、这种方法跨域的兼容性好,由于需要一些额外的设置,所以删除cookie的时候比较繁琐 | 必须保证两个域名为关联域名 |
postMessage跨域 | 1、这种方法可以直接实现将数据从A站点传输到B站点,而且解除了cookie的限制和JSONP无法获取要传入的站点的信息 | 这个为HTML5新增加的特性,浏览器的兼容性不是很好,低版本的浏览器不支持,具体可以见:CANIUSE网站 |
JSONP跨域主要的依据是利用一些HTML标签的“漏洞”,然后通过跨域的方式去调用这个在别的域名下面的脚本文件,JSONP跨域有script跨域
我们先来一个简单的例子,我们先下载一个phpStudy,然后配置两个本地服务器,分别为:www.test1.com、www.test2.com
在www.test1.com域名下面我们添加一个test.js文件,内容如下:
alert("test!");
然后在www.test2.com域名下面我们添加一个HTML文件,如下所示:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script src="http://www.test1.com/test.js" /> </body> </html>
这个相信大多数人都是见过的,可能你会说这个不就是简单的script脚本的引入吗?对的,就是因为script支持跨域,这个也就是我们常见的CDN。但是上面的例子只能说是一个简单的JSONP跨域的应用而已,跨域的目的是要实现数据的传输。我们可以按照这个思路这样去改写:
首先我们可以在一个域名下面书写一个方法,然后通过在外部加载一个脚本来调用这个方法,向这个方法中去传值,这样就可以实现把外部的数据传到当前的域名的下面,从而实现了跨域。
我们在test.js文件下面定义一个
test("tthis is js load script");
www.test2.com下面的HTML文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script type="text/javascript"> var test=function(data){ alert(data); } </script> <script type="text/javascript" src="DEMO.js"></script> </body> </html>
这个我们可以理解为是在一个HMTL文件中书写一个test方法,然后test方法允许调用的时候向里面传参,之后通过外部加载一个JS文件,JS文件实现的逻辑是调用这个方法,所以我们不能够把定义的test方法放在调用的DEMO.js的文件后面,因为如果我们这样做了的话,那么在调用外部的JS文件的时候,test方法没有加载进来所以我们便看不到效果所在,这个有兴趣的同学可以自己去试验一下,这里就不做试验了。
在实际的应用中,这样的例子是基本上不可能看到的,因为这样的参数传递太过于死板,应用性不强,我们在实际的应用中应该要达到的效果是可以根据我们的需求做出相应的响应,这样说吧,大家可能都知道或者调用过接口吧,接口其实我们可以理解为就是JSONP的一种应用,接下来我们就来试验一下:
说了这么多,大家应该还是对具体怎样使用有点云里雾里的吧,现在我们就基于JSONP的思想来制作一个简易的音乐专辑查询器
首先我们应该要找到一个可以查询专辑的公共接口
http://cgi.music.soso.com/fcgi-bin/fcg_search_xmldata.q?source=10&w=关键字&perpage=1&ie=utf-8
然后我们就编写如下的HTML代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>JSONP2</title> </head> <body> <input type="text" id="song" name=""> <input type="button" id="song_search" value="歌曲搜索" name=""> <br /> <div style="width:200px;height:200px;background:pink" id="song_list"></div> <script type="text/javascript" src="http://code.jquery.com/jquery-2.1.1.min.js"></script> <script type="text/javascript"> var searchJsonCallback=function(data){ //遍历查询结果 var alb_html=''; for(var i in data.list){ alb_html+='<span>专辑:</span><div style="color:black">'+data.list[0].albumname+'</div>'; } $("#song_list").html(alb_html); } $("#song_search").on("click",function(){ var keyword=$("#song").val(); if(keyword==undefined||keyword==""){ alert("歌曲搜索不能为空"); return false; }else{ var url = "http://cgi.music.soso.com/fcgi-bin/fcg_search_xmldata.q?source=10&w="+keyword+"&perpage=1&ie=utf-8"; // 创建script标签,设置其属性 var script = document.createElement('script'); script.setAttribute('src', url); // 把script标签加入head,此时调用开始 document.getElementsByTagName('head')[0].appendChild(script); } }) </script> </body> </html>
效果如下:
这个就是JSOP在实际中的应用,如果直接用ajax来实现的话,原理也是一样,只不过要把script标签换成ajax调用而已。
如果你要全部使用jquery来实现的话,那么只需要把ajax中的dataType类型换成jsonp即可(通过上面的例子我们知道script加载实际上也是一个get请求,这个可以在network中验证),这个有兴趣的同学可以查一查资料。
iframe跨域的原理跟script跨域一样,但是我们要注意的是标签自身功能的差异性,具体差异如下:
1、script单纯就是引入的作用,但是iframe标签还有一个作用是显示的作用可以把远程加载的HTML代码显示出来,也就是script无法引入HTML代码文件
2、script标签只能够从远程获取数据,无法操作远程文件执行。但是iframe可以这样
上面的第二点说起来有点难理解,我们就通过一个例子来说明一下:
假设有这样的一个需求我们需要在www.jsonp1.com下面调用一个方法来清除josnp2.com下面的本地存储,
首先我们在jsonp1.com下面的index.html中加入script标签去调用jsonp2.com下面的js文件
具体如下:
window.localStorage.clear();
alert(1);
结果是:
我们可以看到内容为1的弹窗,但是在jsonp2的本地存储就没有被清除,所以我们可以得出结论,script标签的跨域适用于从远程获取数据,不适用对远程的操作。
这个需求我们可以使用iframe标签来实现:
jsonp1.com中的HTML文件如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>jsonp1</title> </head> <body> <iframe src="http://www.jsonp2.com/demo.html"></iframe> </body> </html>
jsonp2下面的demo.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script type="text/javascript"> window.localStorage.clear(); alert("清除成功"); </script> </body> </html>
这样我们就实现了通过在JSONP1中调用JSONP2中的文件来清除JSONP2中的本地存储
但是大家想一想,我们除了使用这种方法来控制远程操作之外,我们还可以像script跨域一样来获取本地存储的数据吗?
答案是理论上是不可以实现的(本例子为博主的思路的构思,没有具体实践过,如有错误望各位指出),如图所示:
首先我们就在jsonp1.com网站下面的index.html文件下面通过iframe插入jsonp2.com/index.html,这样我们就使用了iframe跨域,但是由于同源策略的限制,所以无法将jsonp2.com/index.html的值回传给jsonp1.com/index.html,所以这个时候在jsonp2.com/index.html是可以获取到站点的本地存储的,我们就可以像上面音乐接口去使用,把本地储存中的数值,传递过去,但是这个时候jsonp1.com/index.html文件是无法直接获取接口中返回的东西的,也无法通过jsonp2.com/index.html回调,所以这种方法是不可行的
但是我们就真的无法实现这样一个获取远程的站点的本地存储的功能吗?不是的这个时候利用window.name方法结合iframe来实现跨域
www.jsonp1.com下面的index.html代码:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head> <title>跨域获取数据</title> <script type="text/javascript"> function domainData(url, fn) { var isFirst = true; var iframe = document.createElement('iframe'); iframe.style.width=0; iframe.style.height=0; iframe.style.display = 'none'; var loadfn = function(){ if(isFirst){ iframe.contentWindow.location = 'http://www.jsonp1.com/proxy.html'; isFirst = false; } else { //alert(1); //console.log(iframe.contentWindow.name); alert(iframe.contentWindow.name); iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); iframe.src = ''; iframe = null; } }; iframe.src = url; if(iframe.attachEvent){ iframe.attachEvent('onload', loadfn); } else { iframe.onload = loadfn; } document.body.appendChild(iframe); } </script> </head> <body> </body> <script type="text/javascript"> domainData('http://www.jsonp2.com/demo.html', function(data){ alert(data); }); </script> </html>
www.jsonp2.com/demo.html代码如下:
<script type="text/javascript"> window.localStorage.setItem("test","123"); var data=window.localStorage.getItem("test"); window.localStorage.clear(); window.name=data; </script>
这样我们便可以实现在JSONP1的域名下面访问到JSONP2中的本地存储了,大家特别高兴有没有,反正这个需求当时做得时候也是挺困扰我的。
这里有一些注意事项要特别说明:
1、这个是在网上经过查找的代码,也可以说是一个比较标准的使用代码,大家在使用的时候可以参照业务需求进行该造。
2、在使用iframe这个标签之后要进行销毁,避免出现安全问题
3、window.name的使用必须建立在http协议的基础之上的,换句话来说就是不能直接打开网页一定要配置相应的本地域名(直接打开本地网页采用的是file协议)
这个的实现思路跟cookie跨域相似,都是在两个关联域名中设置document.domain值,然后让这两个值相等,这样就可以实现跨域操作,具体实现不给出,自行百度
cookie跨域这个没有什么好说的,不清楚的同学请看我之前写过的一篇文章:cookie学习指南
我们还是来实现一个上面的功能,在jsonp1域名下面获取jsonp2中localStorage的test字段的值,尝试着用postMessage来实现,具体的实现方式如下:
jsonp1.com下面的index.htm如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div id="test"></div> <textarea id="textarea"></textarea> <iframe style="width:0px;height:0px" id="f" src="http://www.jsonp2.com/demo.html"></iframe> <script type="text/javascript" src="http://cdn.bootcss.com/jquery/3.1.1/jquery.js"></script> <script> var test1=''; onmessage=function(e){ e=e||event; // console.log(e); // console.log(e.data); test1=e.data; if(test1=="123"){ alert("success!"); }else{ alert("error"); } $("#test").html("<span style='color:red'>"+test1+"</span>"); }; </script> </body> </html>
jsonp2.com中的demo.html内容:
<iframe id="f" src="http://www.jsonp1.com/index.html"></iframe> <script> var f=document.getElementById("f"); f.onload=function(){ window.localStorage.setItem("test","123"); var value=window.localStorage.getItem("test"); window.localStorage.clear(); f.contentWindow.postMessage(value,"http://www.jsonp1.com"); } </script>
这样就实现了一个从Jsonp2中获取本地存储的功能,但是在实践的过程中存在的一些问题需要引起我们的留意:
1、在两个需要跨域的文件都需要引入一个iframe来加载对方的路径
2、我们在使用的时候,是使用postMessage来发送信息给对方,然后我们是通过监控message事件来获取信息的
三、总结
这个篇文章主要总结了一些关于前端跨域方面的工作实践,与一些问题的探索,同时如果发现错误的话, 也希望各位能够指正。