Loading... #Js本身的跨域# Js跨域的跨域,这是个面试 工作都会遇到的坑。 那既然这玩意儿这么烦,那我就索性理清一下。 ##首先提一下同源策略## > 同源策略(same-origin policy)。简单来讲同源策略就是浏览器为了保证用户信息的安全,防止恶意的网站窃取数据,禁止不同域之间的JS进行交互。对于浏览器而言只要域名、协议、端口其中一个不同就会引发同源策略,从而限制他们之间如下的交互行为: > 1.Cookie、LocalStorage和IndexDB无法读取; > 2.DOM无法获得; > 3.AJAX请求不能发送。 ### 注 ### 第一,如果是协议和端口造成的跨域问题“前台”是无能为力的, 第二:在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。 > “URL的首部”指window.location.protocol+window.location.host,也可以理解为“Domains, protocols and ports must match”。 ##解决方法## 1.通过修改document.domain来跨子域 (只有在主域相同的时候才能使用该方法) 两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain共享 Cookie或者处理iframe。比如A网页是http://www.baidu.com/a.html,B网页是http://www.baidu.com/b.html,那么只要设置相同的document.domain,两个网页就可以共享Cookie。 ```javascript document.domain = 'a.com'; var ifr = document.createElement('iframe'); ifr.src = 'http://www.baidu.a.com/b.html'; ifr.display = none; document.body.appendChild(ifr); ifr.onload = function(){ var doc = ifr.contentDocument || ifr.contentWindow.document; //在这里操作doc,也就是b.html ifr.onload = null; }; ``` 2.跨域资源共享(CORS) CORS(Cross-Origin Resource Sharing)跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。 服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。 只需要在后台中加上响应头来允许域请求!在被请求的Response header中加入以下设置,就可以实现跨域访问了 ```javascript //指定允许其他域名访问 'Access-Control-Allow-Origin:*'//或指定域 //响应类型 'Access-Control-Allow-Methods:GET,POST' //响应头设置 'Access-Control-Allow-Headers:x-requested-with,content-type' ``` 3.通过jsonp跨域 JSONP是JSON with Padding(填充式json)的简写,是应用JSON的一种新方法,只不过是被包含在函数调用中的JSON,例如: callback({"name","trigkit4"}); JSONP由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数,而数据就是传入回调函数中的JSON数据。 JSONP的原理:通过script标签引入一个js文件,这个js文件载入成功后会执行我们在url参数中指定的函数,并且会把我们需要的json数据作为参数传入。所以jsonp是需要服务器端的页面进行相应的配合的。(即用JavaScript动态加载一个script文件,同时定义一个callback函数给script执行而已。) ```javascript function handleResponse(response){ console.log('The responsed data is: '+response.data); } var script = document.createElement('script'); script.src = 'http://www.baidu.com/json/?callback=handleResponse'; document.body.insertBefore(script, document.body.firstChild); /*handleResonse({"data": "zhe"})*/ //原理如下: //当我们通过script标签请求时 //后台就会根据相应的参数(json,handleResponse) //来生成相应的json数据(handleResponse({"data": "zhe"})) //最后这个返回的json数据(代码)就会被放在当前js文件中被执行 //至此跨域通信完成 ``` > JSONP的优点是:它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果。 > > JSONP的缺点则是:它只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。 4.使用window.name进行跨域 window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。 1) 创建a.com/cs1.html 2) 创建a.com/proxy.html,并加入如下代码 ```javascript <head> <script> function proxy(url, func){ var isFirst = true, ifr = document.createElement('iframe'), loadFunc = function(){ if(isFirst){ ifr.contentWindow.location = 'http://a.com/cs1.html'; isFirst = false; }else{ func(ifr.contentWindow.name); ifr.contentWindow.close(); document.body.removeChild(ifr); ifr.src = ''; ifr = null; } }; ifr.src = url; ifr.style.display = 'none'; if(ifr.attachEvent) ifr.attachEvent('onload', loadFunc); else ifr.onload = loadFunc; document.body.appendChild(iframe); } </script> </head> <body> <script> proxy('http://www.baidu.com/', function(data){ console.log(data); }); </script> </body> ``` 3)在b.com/cs1.html中包含: ```javascript <script> window.name = '要传送的内容'; </script> ``` 5.使用HTML5的window.postMessage方法跨域 HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。 这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。目前IE8+、FireFox、Chrome、Opera等浏览器都已经支持window.postMessage方法。 1) a.com/index.html中的代码: ```javascript <iframe id="ifr" src="b.com/index.html"></iframe> <script type="text/javascript"> window.onload = function() { var ifr = document.getElementById('ifr'); var targetOrigin = 'http://b.com'; // 若写成'http://b.com/c/proxy.html'效果一样 // 若写成'http://c.com'就不会执行postMessage了 ifr.contentWindow.postMessage('I was there!', targetOrigin); }; </script> ``` 2) b.com/index.html中的代码: ```javascript <script type="text/javascript"> window.addEventListener('message', function(event){ // 通过origin属性判断消息来源地址 if (event.origin == 'http://a.com') { alert(event.data); // 弹出"I was there!" alert(event.source); // 对a.com、index.html中window对象的引用 // 但由于同源策略,这里event.source不可以访问window对象 } }, false); </script> ``` 6.通过WebSocket进行跨域 web sockets是一种浏览器的API,它的目标是在一个单独的持久连接上提供全双工、双向通信。(同源策略对web sockets不适用) web sockets原理:在js创建了web socket之后,会有一个HTTP请求发送到浏览器以发起连接。取得服务器响应后,建立的连接会使用HTTP升级从HTTP协议交换为web sockt协议。 只有在支持web socket协议的服务器上才能正常工作。 ```javascript var socket = new WebSockt('ws://www.baidu.com');//http->ws; https->wss socket.send('hello WebSockt'); socket.onmessage = function(event){ var data = event.data; ``` 7.图像ping(单向) 什么是图像ping: 图像ping是与服务器进行简单、单向的跨域通信的一种方式,请求的数据是通过查询字符串的形式发送的,而相应可以是任意内容,但通常是像素图或204相应(No Content)。 图像ping有两个主要缺点:首先就是只能发送get请求,其次就是无法访问服务器的响应文本。 使用方法: ```javascript var img = new Image(); img.onload = img.onerror = function(){ alert("done!"); }; img.src = "https://raw.githubusercontent.com/zhangmengxue/Todo-List/master/me.jpg"; document.body.insertBefore(img,document.body.firstChild); ``` 与<img>类似的可以跨域内嵌资源的还有: ``` (1)<script src=""></script>标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到。上面jsonp也用到了呢。 (2) <link src="">标签嵌入CSS。由于CSS的松散的语法规则,CSS的跨域需要一个设置正确的Content-Type消息头。不同浏览器有不同的限制: IE, Firefox, Chrome, Safari (跳至CVE-2010-0051)部分 和 Opera。 (3)<video> 和 <audio>嵌入多媒体资源。 (4)<object>, <embed> 和 <applet>的插件。 (5)@font-face引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。 (6) <frame> 和 iframe载入的任何资源。站点可以使用X-Frame-Options消息头来阻止这种形式的跨域交互。 ``` 8.使用片段识别符来进行跨域 片段标识符(fragment identifier)指的是,URL的#号后面的部分,比如http://example.com/x.html#flag的#flag。如果只是改变片段标识符,页面不会重新刷新。 父窗口可以把信息,写入子窗口的片段标识符。在父窗口写入: ```javascript document.getElementById('frame').onload = function(){ var src = "http://127.0.0.1/JSONP/b.html" + '#' + "data"; this.src = src; } ``` 子窗口通过监听hashchange事件得到通知: ```javascript window.onload = function(){ console.log("b.html加载完成") window.onhashchange = function(){ var message = window.location.hash; console.log(message)//#data }; } ``` 同样的,子窗口也可以改变父窗口的片段标识符: ```javascript parent.location.href= target + "#" + hash; ``` #webpack的跨域# 需要使用一个webpack的插件:webpack-dev-server 如下: ```javascript module.exports = { devServer: { host: "0.0.0.0", port: 8080, proxy: { "/api": { target: "http://localhost:8081/", ws: true, changeOrigin: true, pathRewrite: { "^/api": "" } }, "/download": { target: "http://localhost:8081/", ws: true, changeOrigin: true, pathRewrite: { "^/download": "" } } } } }; ``` ###参数说明### 2.1 ‘/api’ 捕获API的标志,如果API中有这个字符串,那么就开始匹配代理, 比如API请求/api/users, 会被代理到请求 http://www.baidu.com/api/users 。 2.2 target 代理的API地址,就是需要跨域的API地址。 地址可以是域名,如:http://www.baidu.com 也可以是IP地址:http://127.0.0.1:3000 如果是域名需要额外添加一个参数changeOrigin: true,否则会代理失败。 2.3 pathRewrite 路径重写,也就是说会修改最终请求的API路径。 比如访问的API路径:/api/users, 设置pathRewrite: {’^/api’ : ‘’},后, 最终代理访问的路径:http://www.baidu.com/users, 这个参数的目的是给代理命名后,在访问时把命名删除掉。 2.4 changeOrigin 这个参数可以让target参数是域名。 2.5 secure secure: false,不检查安全问题。 设置后,可以接受运行在 HTTPS 上,可以使用无效证书的后端服务器。 > 具体可以看文章尾部参考资料内的【http-proxy-middleware官方文档】 #gulp的跨域# 1.npm init初始化 2.全局安装gulp npm i gulp -g 3.本地下载gulp包 npm i gulp --save-dev 4.下载gulp-webserver包 npm i gulp-webserver --save-dev 5.新建gulpfile.js文件 ```javascript var gulp = require('gulp'); var webserver = require('gulp-webserver'); gulp.task('webserver', function () { gulp.src('./') .pipe(webserver({ host: 'localhost', port: 8888, livereload: true, // 实时重新加载 open: './index.html', // 启动时默认浏览器打开的文件 directoryListing: { enable: true, path: './' }, proxies: [{ source: '/api', target: 'http://www.your.com' // 代理的域名 }] })) }); // gulp4.0 gulp.task('default', gulp.series(['webserver'], function () { console.log('gulp webserver start successfully at localhost:8888'); })); // 因为gulp升级到了4.0,所以使用gulp.series来启动,如果使用的是gulp3.*的话请将上面的代码替换为下面的 // gulp 3.* gulp.task('default', ['webserver'], function () { console.log('gulp webserver start successfully at localhost:8888'); }); ``` 在项目根目录打开命令窗口,使用命令gulp启动 使用: 如果接口是https://www.yoururl.com/login/index 则发起请求时使用/api/login/index > 具体可以看文章尾部参考资料内的【http-proxy-middleware官方文档】 ##参考资料## <span class="external-link"><a href="https://blog.csdn.net/lareinalove/article/details/84107476" target="_blank">js中的跨域问题<i data-feather='external-link'></i></a></span> <span class="external-link"><a href="https://www.cnblogs.com/chris-oil/p/9609398.html" target="_blank">js前端解决跨域问题的8种方案(最新最全)<i data-feather='external-link'></i></a></span> <span class="external-link"><a href="https://www.npmjs.com/package/http-proxy-middleware" target="_blank">http-proxy-middleware官方文档<i data-feather='external-link'></i></a></span> Last modification:January 21st, 2020 at 11:42 am © 允许规范转载 Support 如果觉得我的文章对你有用,请随意赞赏 ×Close Appreciate the author Sweeping payments Pay by AliPay Pay by WeChat
CORS(Cross-Origin Resource Sharing)感觉还是这种最简单了,其他都好复杂
对的,所以跨域问题如果交给后端处理的话还真的就只有几行代码的事情