参考:https://blog.csdn.net/frontender_way/article/details/70568113 javascript
1、同源策略:php
同源策略就是浏览器为了保证用户信息的安全,防止恶意的网站窃取数据,禁止不一样域之间的JS进行交互。html
对于浏览器而言只要域名、协议、端口其中一个不一样就会引起同源策略,从而限制他们之间以下的交互行为:前端
(1)Cookie、LocalStorage 和 IndexDB 没法读取。
(2)DOM 没法得到。
(3)AJAX 请求不能发送。html5
2、什么是跨域?java
协议、主机地址(或域名)、端口其中有一项不一样,就认为是不一样域,这之间发生任何数据交互都是跨域。node
3、什么是JS跨域?jquery
两个不一样的域a、bnginx
在a的应用的js脚本中调用了b的后端地址。 web
js跨域是指经过js在不一样的域之间进行数据传输或通讯,好比用ajax向一个不一样的域请求数据,或者经过js获取页面中不一样域的框架中(iframe)的数据。
只要协议、域名、端口有任何一个不一样,都被看成是不一样的域。
4、有哪些跨域方法?
(1)JSONP
问题: 假设 a 网站请求 b 网站的一个 js,这个 js 中请求了 b 网站的内容算跨域吗。
在js中,咱们直接用XMLHttpRequest请求不一样域上的数据时,是不能够的。
可是,在页面上引入不一样域上的js脚本文件倒是能够的,script标签里的src属性来完成的,jsonp正是利用这个特性来实现的。
好比,新建一个crossDomain.html页面,它里面的代码须要利用ajax获取一个不一样域上的json数据,假设这个json数据地址是http://192.168.x.xxx/JSONP/jsonpTest.php那么crossDomain.html中的代码就能够这样:
<script type="text/javascript"> var text = document.querySelector('.text'); function dosomething(jsondata) { var str = ""; for (var i = 0; i < jsondata.length; i++) { str += jsondata[i]; } text.innerHTML = '我是JS经过JSONP跨域请求来的数据:'+'<span class="show">'+str+'</span>'; } </script> <script type="text/javascript" src="http://192.168.x.xxx/JSONP/jsonpTest.php?callback=dosomething"></script>
能够看到在获取数据的地址后面还有一个callback参数,按惯例是用这个参数名,可是你用其余的也同样。固然若是获取数据的jsonp地址页面不是你本身能控制的,就得按照提供数据的那一方的规定格式来操做了。
由于是当作一个js文件来引入的,因此http://192.168.x.xxx/JSONP/jsonpTest.php返回的必须是一个能执行的js文件,因此这个页面的php代码多是这样的:
<?php $callback = $_GET['callback'];//获得回掉函数名 $data = array('a','b','c'); //要返回的数据 echo $callback.'('.json_encode($data).')'; //输出 ?>
而后在crossDomain.html中打印出返回的jsondata以下:
["a", "b", "c"]
能够看到请求成功了,而后就能够在crossDomain.html这个页面里处理这个数据了。
这样jsonp的原理就很清楚了,经过script标签引入一个js文件,这个js文件载入成功后会执行咱们在url参数中指定的函数,而且会把咱们须要的json数据做为参数传入。因此jsonp是须要服务器端的页面进行相应的配合的。
固然能够直接用一些已经封装过的库,这样就不用每次去建立script标签了。以下为JQ的跨域API:
$.getJSON('http://192.168.x.xxx/JSONP/jsonpTest.php?callback=?',function(jsondata){ console.log(jsondata);//["a", "b", "c"] var str = ""; $.each(jsondata,function(i,index){ return str += index; }); $(".text1").html('我是JQ经过JSONP跨域请求来的数据:'+'<span class="show">'+str+'</span>'); });
jquery的getJSON方法会自动生成一个全局函数来替换callback=?中的问号,以后获取到数据后又会自动销毁,实际上就是起一个临时代理函数的做用。$.getJSON方法会自动判断是否跨域,不跨域的话,就调用普通的ajax方法;跨域的话,则会以异步加载js文件的形式来调用jsonp的回调函数。
var script = document.createElement('script'); script.type = 'text/javascript'; // 传参并指定回调执行函数为onBack script.src = 'http://www.domain-com:8080/login?user=admin&callback=onBack'; document.head.appendChild(script); // 回调执行函数 function onBack(res) { alert(JSON.stringify(res)); }
(2)经过修改document.domain来跨子域
上面的jsonp是来解决ajax跨域请求的,那么若是是须要处理 Cookie 和 iframe 该怎么办呢?这时候就能够经过修改document.domain来跨子域。两个网页一级域名相同,只是二级域名不一样,浏览器容许经过设置document.domain共享 Cookie或者处理iframe。好比A网页是http://w1.example.com/a.html,B网页是http://w2.example.com/b.html,那么只要设置相同的document.domain,两个网页就能够共享Cookie。
document.domain = 'example.com'; //如今,A网页经过脚本设置一个 Cookie。 document.cookie = "test1=hello"; //B网页就能够读到这个 Cookie。 var allCookie = document.cookie;
注意,这种方法只适用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 没法经过这种方法,规避同源政策,而要使用下文介绍的PostMessage API。
另外,服务器也能够在设置Cookie的时候,指定Cookie的所属域名为一级域名,好比.example.com。
Set-Cookie: key=value; domain=.example.com; path=/ //这样的话,二级域名和三级域名不用作任何设置,均可以读取这个Cookie。
不一样的iframe 之间(父子或同辈),是可以获取到彼此的window对象的,可是你却不能使用获取到的window对象的属性和方法(html5中的postMessage方法是一个例外,还有些浏览器好比ie6也可使用top、parent等少数几个属性),总之,你能够当作是只能获取到一个几乎无用的window对象。
首先说明一下同域之间的iframe是能够操做的。好比http://127.0.0.1/JSONP/a.html里面嵌入一个iframe指向http://127.0.0.1/myPHP/b.html。那么在a.html里面是能够操做iframe里面的DOM的。
<iframe src="http://127.0.0.1/myPHP/b.html" frameborder="1"></iframe> <body> <script type="text/javascript"> var iframe = document.querySelector("iframe"); iframe.onload = function(){ var win = iframe.contentWindow; var doc = win.document; var ele = doc.querySelector(".text1"); var text = ele.innerHTML="123456"; } </script>
若是两个网页不一样源,就没法拿到对方的DOM。典型的例子是iframe窗口和window.open方法打开的窗口,它们与父窗口没法通讯。若是两个窗口一级域名相同,只是二级域名不一样,那么document.domain属性,就能够规避同源政策,拿到DOM。
对于彻底不一样源的网站,目前有三种方法,能够解决跨域窗口的通讯问题。
(3)使用片断识别符来进行跨域
片断标识符(fragment identifier)指的是,URL的#号后面的部分,好比http://example.com/x.html#fragment的#fragment。若是只是改变片断标识符,页面不会从新刷新。
父窗口能够把信息,写入子窗口的片断标识符。在父窗口写入:
document.getElementById('frame').onload = function(){ var src = "http://127.0.0.1/JSONP/b.html" + '#' + "data"; this.src = src; }
子窗口经过监听hashchange事件获得通知。
window.onload = function(){ console.log("b.html加载完成") window.onhashchange = function(){ var message = window.location.hash; console.log(message)//#data }; }
一样的,子窗口也能够改变父窗口的片断标识符。
parent.location.href= target + "#" + hash;
(4)使用window.name来进行跨域
window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的全部的页面都是共享一个window.name的,每一个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的全部页面中的,并不会因新页面的载入而进行重置。这个属性的最大特色是,不管是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页能够读取它。
好比:有一个页面a.html,它里面有这样的代码:
window.name = "我是a页面设置的"; setTimeout(function(){ window.location = "http://127.0.0.1/JSONP/b.html"; },1000)
b.html页面的代码:
console.log(window.name);
a.html页面载入后1秒,跳转到了b.html页面,结果b页面打印出了:
我是a页面设置的
能够看到在b.html页面上成功获取到了它的上一个页面a.html给window.name设置的值。若是在以后全部载入的页面都没对window.name进行修改的话,那么全部这些页面获取到的window.name的值都是a.html页面设置的那个值。固然,若是有须要,其中的任何一个页面均可以对window.name的值进行修改。注意,window.name的值只能是字符串的形式,这个字符串的大小最大能容许2M左右甚至更大的一个容量,具体取决于不一样的浏览器,但通常是够用了。
利用window.name能够对同域或者不一样域的之间的js进行交互。
那么在a.html页面中,咱们怎么把b.html页面载入进来呢?显然咱们不能直接在a.html页面中经过改变window.location来载入b.html页面,由于咱们想要即便a.html页面不跳转也能获得b.html里的数据。答案就是在a.html页面中使用一个隐藏的iframe来充当一个中间人角色,由iframe去获取b.html的数据,而后a.html再去获得iframe获取到的数据。
(5)window.postMessage
上面两种方法都属于破解,HTML5为了解决这个问题,引入了一个全新的API:跨文档通讯 API(Cross-document messaging)。
这个API为window对象新增了一个window.postMessage方法,容许跨窗口通讯,不论这两个窗口是否同源。目前IE8+、FireFox、Chrome、Opera等浏览器都已经支持window.postMessage方法。
举例来讲,父窗口http://a.com向子窗口http://b.com发消息,调用postMessage方法就能够了。
a页面:
<iframe id="frame1" src="http://127.0.0.1/JSONP/b.html" frameborder="1"></iframe> document.getElementById('frame1').onload = function(){ var win = document.getElementById('frame1').contentWindow; win.postMessage("我是来自a页面的","http://127.0.0.1/JSONP/b.html") }
b页面经过监听message事件能够接受到来自a页面的消息。
window.onmessage = function(e){
e = e || event;
console.log(e.data);//我是来自a页面的
}
子窗口向父窗口发送消息的写法相似。
window.opener.postMessage('我是来自b页面的', 'http://a.com'); //父窗口和子窗口均可以经过message事件,监听对方的消息。
经过window.postMessage,读写其余窗口的 LocalStorage 也成为了可能。
下面是一个例子,主窗口写入iframe子窗口的localStorage。
父窗口发送消息代码
var win = document.getElementsByTagName('iframe')[0].contentWindow; var obj = { name: 'Jack' }; // 存入对象 win.postMessage(JSON.stringify({key: 'storage', method: 'set', data: obj}), 'http://b.com'); // 读取对象 win.postMessage(JSON.stringify({key: 'storage', method: "get"}), "*"); window.onmessage = function(e) { if (e.origin != 'http://a.com') return; // "Jack" console.log(JSON.parse(e.data).name); };
子窗口接收消息的代码
window.onmessage = function(e) { if (e.origin !== 'http://bbb.com') return; var payload = JSON.parse(e.data); switch (payload.method) { case 'set': localStorage.setItem(payload.key, JSON.stringify(payload.data)); break; case 'get': var parent = window.parent; var data = localStorage.getItem(payload.key); parent.postMessage(data, 'http://aaa.com'); break; case 'remove': localStorage.removeItem(payload.key); break; } };
(6)经过WebSocket进行跨域
WebSocket是一种通讯协议,使用ws://(非加密)和wss://(加密)做为协议前缀。该协议不实行同源政策,只要服务器支持,就能够经过它进行跨源通讯。
下面是一个例子,浏览器发出的WebSocket请求的头信息
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 Origin: http://example.com
上面代码中,有一个字段是Origin,表示该请求的请求源(origin),即发自哪一个域名。
正是由于有了Origin这个字段,因此WebSocket才没有实行同源政策。由于服务器能够根据这个字段,判断是否许可本次通讯。若是该域名在白名单内,服务器就会作出以下回应。
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat
(7)CORS
CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。相比JSONP只能发GET请求,CORS容许任何类型的请求。CORS须要浏览器和服务器同时支持。目前,全部浏览器都支持该功能,IE浏览器不能低于IE10。
整个CORS通讯过程,都是浏览器自动完成,不须要用户参与。对于开发者来讲,CORS通讯与同源的AJAX通讯没有差异,代码彻底同样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感受。
所以,实现CORS通讯的关键是服务器。只要服务器实现了CORS接口,就能够跨源通讯。
(8)服务端设置代理页面专门处理前端跨域请求
总结:以上整理了各类常见的跨域解决办法,在开发过程当中咱们能够根据不一样的场景选择最佳的解决办法。处理ajax的跨域能够选择JSONP、CORS,服务端设置代理、WebSocket。若是主域相同,处理多级子域之间的通讯能够选择document.domain,处理不一样域之间的iframe,子窗口能够选择window.name、window.postMessage、location.hash来解决。
5、怎么发送一个跨域的POST请求?
6、跨域方式
var script = document.createElement('script'); script.type = 'text/javascript'; // 传参并指定回调执行函数为onBack script.src = 'http://www.domain-com:8080/login?user=admin&callback=onBack'; document.head.appendChild(script); // 回调执行函数 function onBack(res) { alert(JSON.stringify(res)); }
//父窗口:(http://www.domain.com/a.html) <iframe id="iframe" src="http://child.domain.com/b.html"></iframe> <script> document.domain = 'domain.com'; var user = 'admin'; </script> //子窗口:(http://child.domain.com/b.html) <script> document.domain = 'domain.com'; // 获取父窗口中变量 alert('get js data from parent ---> ' + window.parent.user); //"admin" </script>
location.hash + iframe
与document.domain相似,不一样的是,经过修改父页面的iframe的src进而达到修改window.hash的效果,子页面经过window.onhashchange来监听
跨域资源共享(CORS)
前端正常请求,后端设置:
res.writeHead(200, { 'Access-Control-Allow-Credentials': 'true', // 后端容许发送Cookie 'Access-Control-Allow-Origin': 'http://www.domain1.com', // 容许访问的域(协议+域名+端口) /* * 此处设置的cookie仍是domain2的而非domain1,由于后端也不能跨域写cookie(nginx反向代理能够实现), * 但只要domain2中写入一次cookie认证,后面的跨域接口都能从domain2中获取cookie,从而实现全部的接口都能跨域访问 */ 'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly的做用是让js没法读取cookie });