当前位置: 首页 > 科技观察

《深入浅出》前端开发常用的几种跨域解决方案

时间:2023-03-19 15:35:29 科技观察

看完本文,你可以系统地掌握不同跨域解决方案的巧妙之处,以及它们的用法、原理、局限性和适用性场景包括以下几个方面:跨域现象,以及几种常见的跨域性能跨域解决方案(原理分析)修改本地HOSTJSONPORSProxyNginx反向代理PostMessage(利用iframe标签实现不同域的关联)什么是同一个来源?如果两个URL的协议protocol、主机名host、端口号port相同,则这两个URL同源。同源策略同源策略是一项重要的安全策略。它阻止恶意文档,减少攻击向量。在实际项目中,很少有同源策略,大部分都是非同源策略。什么是跨域?当协议、域名、端口号中的任何一项不同时,都算作不同域,这种跨域请求资源(非同源策略请求)的表现称为“跨域”。跨域现象那我们来分析下面几个网址,哪个是协议,哪个是域名和端口号http://kbs.sports.qq.com/index.html协议:http(还有一个httpsprotocol)域名:kbs.sports.qq.com端口号:80https://127.0.0.1:3000协议:https域名:127.0.0.1端口号:3000如果我们真实项目开发中的web服务器地址是“http://kbs.sports.qq.com/index.html”,请求的数据接口地址为“http://api.sports.qq.com/list”。当web服务器地址向数据接口地址发送请求时,会造成跨域现象。服务器跨域部署的几种常见表现形式(web服务器+数据请求服务器)本地开发(本地预览项目调用测试服务器data)调用第三方平台的接口web服务器:主要用于静态资源文件处理方案修改本地HOST(未介绍)JSONPCORSProxyNginx反向代理PostMessage(利用iframe标签实现不同域的关联)后面会详细分析这四种方案的原理和使用配置,以及各自的优缺点注意:发送时基于ajax或fetch的请求,如果是跨域,浏览器默认的安全策略会禁止跨域请求补充说明:以下所有测试用例均由Web发起:http://127.0.0.1:5500/index.html到API:http://127.0.0.1:1001/list。API接口的server端是通过express自己建立的,下面在server端以app.use中间件的形式接受并处理客户端的请求。即访问来自“http://127.0.0.1:55”的“http://127.0.0.1:1001/list”上的XMLHttpRequest已被CORS策略阻止:请求中没有“Access-control-allow”resource-origin”header在后端打开一个端口号为1001的服务器,我们来练习letxhr=newXMLHttpRequest;xhr.open('get','http://127.0.0.1:1001/list');xhr.onreadystatechange=()=>{if(xhr.status===200&&xhr.readyState===4){console.log(xhr.responseText);}};xhr.send();这里提示常见的跨域错误是由于浏览器默认的安全策略禁止,这里给出几种常见的解决方案JSONP原理:JSONP使用没有域限制的脚本标签,在全局执行上下文(用于接收服务器返回的数据信息)中定义一个函数func来接收数据,从而实现跨域请求。缺点:只允许GET请求不安全:只要浏览器支持,存在于浏览器的全局变量中,任何人都可以调用图形化JSONP原理手动封装JSONP回调必须是全局上下文中的函数(为了防止非全局函数,我们需要把这个函数放在全局,当浏览器从服务器接收信息时,需要执行这个函数)注意:uniqueName变量存放的是全局回调函数(保证每次回调都是唯一的)检查url是否包含“?”,如果有,直接拼接回调,如果没有,则添加“?”//Clientfunctionjsonp(url,callback){//将传入的回调函数挂载到全局letuniqueName=`jsonp${newDate().getTime()}`;//一层匿名函数//目的是让返回的回调执行并删除创建的labelwindow[uniqueName]=data=>{//得到结果从服务器让浏览器执行回调文档t.body.removeChild(script);deletewindow[uniqueName];callback&&callback(data);}//处理URLurl+=`${url.includes('?')}?'&':'?}callback=${uniqueName}'`;//发送请求letscript=document.createElement('script');script.src=url;document.body.appendChild(script);}//执行第二个参数回调获取数据jsonp('http://127.0.0.1:1001/list?userName="lsh"',(result)=>{console.log(result);})//server端//Api请求数据app.get('/list',(req,res)=>{//req.query问号后面传递的参数信息//此时的回调就是传递的函数名(uniqueName)let{callback}=req.query;//数据待返回(字符串)letres={code:0,data:[10,20]};letstr=`${callback}($(JSON.stringify(res)))`;//返回数据给客户端res.send(str);})测试用例展示:客户端请求的url,服务端返回的数据,回调结果返回的数据信息//服务端请求的urlRequestURL:http://127.0.0.1:1001/list?userName="lsh"&callback=jsonp159876002//服务端回调返回的函数jsonp159876002({"code":0,"data":[10,20]});//客户端收到的数据信息{code:0,data:Array(2)}浏览时服务器发现返回的是jsonp159876002({"code":0,"data":[10,20]});这个函数会自动帮我们执行上面提到的JSONP弊端,只要服务器设置为允许jsonp表单跨域请求,我们就可以取回数据。下面是我们封装jsonp方法后,发送一个允许任意源向服务器发送请求的URLxxxjsonp('https://matchweb.sports.qq.com/matchUnion/cateColumns?from=pc',result=>{console.log(结果);});CORS上面说了不允许跨域的根本原因是因为Access-Control-Allow-Origin已经被禁止了,所以只要让server端设置允许的origin即可原理:解决浏览器默认的安全策略是设置服务器端允许哪些源请求。让我们看看以下设置的问题。//服务端app.use((req,res,next)=>{//*AllowAllorigins(不安全/不能携带资源凭证)res.header("Access-Control-Allow-Origin","*");res.header("Access-Control-Allow-Credentials",true);/*res.header("Access-Control-Allow-Headers","Content-Type,...");res.header("Access-Control-Allow-Methods","GET,...");*///测试请求:在CORS跨域请求中,浏览器会先发送测试请求,验证是否可以通信服务器跨域,服务器返回200,浏览器继续发送真正的请求req.method==='OPTIONS'?res.send('CURRENTSERVICESSUPPORTCROSSSDOMAINREQUESTS!'):next();});//Clientletxhr=newXMLHttpRequest;xhr.open('get','http://127.0.0.1:1001/list');xhr.setRequestHeader('Cookie','name=jason');xhr.withCredentials=true;xhr.onreadystatechange=()=>{if(xhr.status===200&&xhr.readyState===4){console.log(xhr.responseText);}};xhr.send();一旦我们在服务端设置了请求允许任何来源,这个请求其实是不安全的,要求客户端不能携带资源凭证(比如上面的Cookie字段),浏览器就会报错告诉我们Cookie字段是不安全的,无法设置,如果允许的来源是'*'则不允许。如果我们真正的项目开发中写对了?设置单源(安全/也可以携带资源凭证/只能单源)或者动态设置多源:每次请求都会经过这个中间件,我们先设置一个白名单,如果当前客户端请求的源在白名单中,我们动态设置Allow-Origin为当前源app.use((req,res,next)=>{//也可以自定义白名单,勾选请求源是否在白名单中,动态设置/*letsafeList=["http://127.0.0.1:5500",xxx,xxxxx,];*/res.header("Access-Control-Allow-Origin","http://127.0.0.1:5500");res.header("Access-Control-Allow-Credentials",true);//设置是否携带资源凭证/*res.header("Access-Control-Allow-Headers","Content-Type,....");res.header("Access-Control-Allow-Methods","GET,...");*///实验请求:交叉CORS域请求中,浏览器会先发送测试请求,验证是否可以通信与服务器跨域。如果服务器返回200,浏览器会继续发送真正的请求CORS的好处是原理简单,易于配置,允许携带资源凭证,仍然使用ajax作为资源请求。可以动态设置多个源。通过判断,设置Allow-Origin为当前源CORS的限制,只允许某个源发起请求,比如多个源,还需要动态判断ProxyProxy翻译成“代理”。是webpack配置的一个插件叫“webpack-dev-server”(只在开发环境下可用)webpack中代理配置constpath=require('path');constHtmlWebpackPlugin=require('html-webpack-plugin');module.exports={mode:'production',entry:'./src/main.js',output:{...},devServer:{port:'3000',compress:true,open:true,hot:true,proxy:{'/':{target:'http://127.0.0.1:3001',changeOrigin:true}}},//配置WEBPACK的插件plugins:[newHtmlWebpackPlugin({template:`./public/index.html`,filename:`index.html`})]};图ProxyProxy代理的原理其实相当于webpack-dev-server配置在本地创建一个port=3000的服务,使用node的中间层代理(分发)来解决浏览器的同源策略限制,但是只能是用于开发环境,因为dev-server只是一个webpack的插件;如果需要在生产环境中使用,我们需要配置Nginx反向代理服务器;另外,如果是自己实现node服务层代理:开发环境和生产环境都可以处理(node中间层和client同源,中间层帮我们从请求数据服务器,然后返回数据给客户端)Proxy的局限性只能在本地开发阶段配置Nginx反向代理,主要作为生产环境的跨域解决方案。原理:利用Node中间层的分发机制,将请求的URL重定向到服务器端地址,配置反向代理服务器{listen:80;server_name:192.168.161.189;loaction:{proxy_pass_http://127.0.0.1:1001;//request转这个URL地址,服务器地址roothtml;indexindex.html;}}简单写个伪代码,实际开发中根据需要配置即可。POSTMESSAGE假设现在有两个页面,即A页面port=1001和B页面port=1002,要实现页面A和页面B之间的页面通信(跨域)原理:将页面B作为A的子页面嵌入到A页面,通过iframe.contentWindow.postMessage传递一些信息给B页面,在A页面通过window.onmessage获取A页面的信息ev.data(见下面代码),同样在B页面传递ev.source.postMessage向A页面传递信息。在A页面中,通过window.onmessage获取B页面传递的信息。主要是利用内置的postMessage和onmessage来传递和接收信息。A.html//将页面B作为A的子页面嵌入到页面AB.html方案比较1.JSONP方案需要前后端共同配置(使用script标签没有域限制)【麻烦,老项目都用】2.CORS原理简单,但只有单一来源可以配置。如果需要配置多个源,只能从白名单中选择一个满足表要求的源。[偶尔使用]服务端需要单独处理,客户端比较简单。3、代理客户端通过dev-server,生产环境需要配置Nginx反向代理(使用Node中间层分发机制)【常用】