为什么会有同源策略的概念?1995年,Netscape引入了一项安全策略,现在所有浏览器都使用该策略。它限制一个来源向另一个来源请求资源,并用于隔离潜在的恶意文件。什么是不同的来源?我们都知道一般地址由以下三部分组成:相同的协议、相同的域名、相同的端口、相同的端口。只要两个地址之一不同,那么这两个地址就是不同的来源。例如//地址http://www.address.com/item/page.html协议:http://域名:www.example.com端口:8080(http)/443(https)(端口省略)默认)http://www.address.com/item2/other.html:同源http://address.com/item/other.html:不同源(不同域名)http://v2.www.address.com/item/other.html:不同来源(不同域名)http://www.address.com:81/item/other.html:不同来源(不同端口)同源的目的是为了保护用户信息的安全并防止恶意网站窃取cookie可以共享的数据。一些网站一般会将一些重要信息存储在cookies或LocalStorage中。这个时候,如果其他网站能够获取到这些数据,可想而知,安全性毫无保障可言。目前限制的行为有三类:Cookie、LocalStorage、IndexDB不能读取DOM,不能获取AJAX请求,不能发送解决方案能力,我们可以把js、css、img等资源放在一个独立的服务器上域名。然后通过动态创建script标签,然后请求回调函数的url,服务端将需要传递的参数插入到回调函数中,然后我们在js代码中执行这个函数就可以得到你想要的参数了。前端原生实现jquery实现$.ajax({type:"get",url:"https://www.address.com:433/json",dataType:"jsonp",jsonp:"callback",jsonpCallback:"xxx"//自定义回调函数名success:function(res){console.log(res)},error:function(){console.log('失败');}});jsonpplugin//安装npminstalljsonpconstjsonp=require('jsonp');jsonp('https://www.address.com:433/json',{parma:'xxx'},(err,data)=>{if(err){console.error(err.message);}else{console.log(data);}});node.jseggserver//需要看egg官网搭建一个简单的框架egg-init--type=simple//router.js使用egg内置的jspnp方法https://eggjs.org/api/config.html#jsonpmodule.exports=app=>{app.get('/json',app.jsonp({callback:'xxx'}),app.controller.json.index)}缺点只支持GET请求,不支持支持POST请求调用失败没有HTTP状态码不够安全2、CORS原理CORS(Cross-OriginResourceSharing)跨资源共享,浏览器不能低于IE10,服务端支持任何类型的请求。熟悉后台需要设置的几个字段,Access-Control-Allow-Origin字段是必须要传的,*表示允许任何域名的请求。当需要传递cookie时,需要指定域名。Access-Control-Allow-Credentials字段可选,默认为false,表示是否允许发送cookie。如果允许,通知浏览器也启用cookie值的传送。Access-Control-Expose-Headers字段是可选的。如果想让浏览器获取getResponesHeader()的其他字段,就在这里指定。Access-Control-Request-Method是必填字段,不是简单的请求,比如PUT请求。Access-Control-Request-Headers指定附加的发送头信息,以逗号分隔。前端原生代码varxhr=newXMLHttpRequest()//设置携带cookiexhr.withCredentials=true;xhr.open('POST','https://www.address.com:433/json',true);xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')xhr.send(null)xhr.onreadystatechange=function(){if(xhr.readyState===4&&xhr.status===200){console.log(JSON.parse(xhr.responseText).msg)}}后端代码module.exports=app=>{classCrosControllerextendsapp.Controller{*index(req){this.ctx.set('访问控制允许来源','https://www.address.com');this.ctx.set('Access-Control-Allow-Credentials','true')this.ctx.body={msg:'helloworld'}}}returnCrosController}Advantage支持所有类型的HTTP请求。缺点是不兼容IE10及以下版本。3、iframe结合location.hash方法的原理也是利用了iframe可以在不同域传值的特性,而location.hash正好可以携带参数,所以用iframe作为不同域之间的桥梁。具体实现步骤1、在A域名页面中插入一个B域名的iframe标签。2、然后在B域名的iframe页面中,ajax请求同域名的服务器。3、iframe页面获取到数据后,使用praent.location.href把你要传递的参数放在#后面,以hash形式传递。4、这样在A域名页面,就可以通过window.onhashchange监控处理你想要的数据了。一个域名页面variframe=document.createElement('iframe')iframe.src='http://www.B.com:80/hash.html'document.body.appendChild(iframe)window.onhashchange=function(){//处理hashconsole.log(location.hash)}B域名页面varxhr=newXMLHttpRequest()xhr.onreadystatechange=function(){if(xhr.readyState===4&&xhr.status===200){varres=JSON.parse(xhr.responseText)console.log(res.msg)parent.location.href=`http://www.A.com:80/a.html#msg=${res.msg}`}}xhr.open('GET','http://www.B.com:80/json',true)xhr.send(null)缺点虽然iframe可以解决问题,但是安全隐患更重要的是。通过hash传递参数处理起来比较麻烦。4、iframe结合window.name方法的原理其实和上面的方法是一样的,不同的是window.name可以传输2MB以上的数据。一个域名页面variframe=document.createElement('iframe')iframe.src='http://www.B.com:80/name.html'document.body.appendChild(iframe)vartimes=0iframe.onload=function(){if(times===1){console.log(JSON.parse(iframe.contentWindow.name))destructionFrame()}elseif(times===0){times=1}}//获取数据后销毁iframe,释放内存;functiondestroyFrame(){document.body.removeChild(iframe);}B域名页面varxhr=newXMLHttpRequest()xhr.onreadystatechange=function(){if(xhr.readyState===4&&xhr.status===200){window.name=xhr.responseTextlocation.href='http://www.A.com:80/a.html'}}xhr.open('GET','http://www.B.com:80/json',true)xhr.send(null)5.postMessage跨域原理postMessage是H5原生API支持的,可以在两个或多个页面,以及不同源页面之间传递数据。窗口之间传递数据的前提是必须从一个窗口获取另一个窗口的目标对象(目标窗口)。例如,要打开另一个带有iframe的窗口,我们必须获取此iframe的contentWindow。或者当通过window.open()打开另一个窗口时,会返回这个窗口的window对象。参数transfer/**data需要传输的数据,使用JSON.stringify序列化。当origin设置为'*'时,表示传递给所有窗口,也可以指定地址。如果设置为与'/'**/postMessage(data,origin)http://localhost:8069window//打开一个新窗口varpopup=window.open('http://localhost:8080');///等待新窗口加载完成setTimeout(function(){//当前窗口向目标源发送数据popup.postMessage({"age":10},'http://localhost:8080');},1000);http://localhost:8080Window//设置监听,如果有数据,打印window.addEventListener('message',function(e){console.log(e);//判断是否是目标地址if(e.origin!=='http://localhost:8069')return;//console.log(e.source===window.opener);//true//发回数据e.source.postMessage({"age":20},e.origin);});其他解决方案以上五种是比较常见的跨域解决方案,各有优缺点。没有绝对的最优方案,只有最适合的应用场景。两种常见的proxy跨域nginx反向代理跨域node中间件proxy配置就不说了,其实我也不熟悉,平时工作中也不会用到这么高大上的。说说原理和思路吧。什么是代理?既然是跨域代理,那么代理服务器(ProxyServer)就是很重要的一点。这里的代理是一个非常重要的服务器安全功能,也是一个非常常见的设计模式。隔离不同的模块,解耦模块。在生活中很容易看到这种模式。当我们买房子(我们买不起)或汽车时,就像是在与一台大型服务器打交道。人家都是大老板,自然没那么容易亲自接待你。这时候中间有一个中介,帮助双方理清思路,打好基础,之后的沟通会更加顺畅和高效。我们的浏览器对不同来源的服务器访问有限制,但是nginx、node中间件等agent没有跨域限制,所以我们可以放心的把任务委托给它们,让它们帮我们做一些我们做不了的事情。为什么代理反了?我们知道单台服务器的处理能力是有限的。就像中国,不可能只有一家汽车制造商。像中国这样大的市场,自然需要众多厂商来满足。那么用户在购买的时候需要节省时间和精力,而汽车就会承担这样一个角色来分发成千上万的用户需求,而nginx可以将用户的请求分发给空闲的服务器,然后服务器返回给自己的服务发送给负载均衡器,然后负载均衡器会将服务器的服务返回给用户,这样我们就不知道是哪个服务器发来的服务了,这样就很好的隐藏了服务器。有一句精辟的话是这样说的:“反向代理是流量发散,代理是流量汇聚”。最后,附上一张非常难看的图。如果还想继续讨论或者学习更多知识,欢迎加入QQ或者微信一起讨论:854280588
