两个不一样域互相请求,称为跨域,是由浏览器同源策略限制的一类请求场景。javascript
--> 同源策略/SOP(Same origin policy)是浏览器最核心也最基本的安全功能,若是缺乏了同源策略,浏览器很容易受到CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同。php
同源策略限制如下几种行为:
1.) Cookie、LocalStorage 和 IndexDB 没法读取<br/>
2.) DOM没法得到<br/>
3.) AJAX 请求不能发送html
目前主流的用于解决跨域问题的方法:前端
缺点:这种方法只适用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 没法经过这种方法。vue
实现原理:两个页面都经过js强制设置document.domain为基础主域,就实现了同域。html5
1.)父窗口:(http://www.domain.com/a.html)java
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe> <script> document.domain = 'domain.com'; var user = 'admin'; </script>
2.)子窗口:(http://child.domain.com/b.html)node
<script> document.domain = 'domain.com'; // 获取父窗口中变量 alert('get js data from parent ---> ' + window.parent.user); </script>
-->iframe是什么?iframe 元素会建立包含另一个文档的内联框架(即行内框架)jquery
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数很少能够跨域操做的window属性之一,它可用于解决如下方面的问题:<br/>
a.) 页面和其打开的新窗口的数据传递<br/>
b.) 多窗口之间消息传递<br/>
c.) 页面与嵌套的iframe消息传递<br/>
d.) 上面三个场景的跨域数据传递web
用法:postMessage(data,origin)方法接受两个参数
*
,表示能够传递给任意窗口,若是要指定和当前窗口同源的话设置为"/"。1.)a.html:(http://www.domain1.com/a.html)
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe> <script> var iframe = document.getElementById('iframe'); iframe.onload = function() { var data = { name: 'aym' }; // 向domain2传送跨域数据 iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com'); }; // 接受domain2返回数据 window.addEventListener('message', function(e) { alert('data from domain2 ---> ' + e.data); }, false); </script>
2.)b.html:(http://www.domain2.com/b.html)
<script> // 接收domain1的数据 window.addEventListener('message', function(e) { alert('data from domain1 ---> ' + e.data); var data = JSON.parse(e.data); if (data) { data.number = 16; // 处理后再发回domain1 window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com'); } }, false); </script>
window.name属性的独特之处:name值在不一样的页面(甚至不一样域名)加载后依旧存在,而且能够支持很是长的 name 值(2MB)。
1.)a.html:(http://www.domain1.com/a.html)
var proxy = function(url, callback) { var state = 0; var iframe = document.createElement('iframe'); // 加载跨域页面 iframe.src = url; // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name iframe.onload = function() { if (state === 1) { // 第2次onload(同域proxy页)成功后,读取同域window.name中数据 callback(iframe.contentWindow.name); destoryFrame(); } else if (state === 0) { // 第1次onload(跨域页)成功后,切换到同域代理页面 iframe.contentWindow.location = 'http://www.domain1.com/proxy.html'; state = 1; } }; document.body.appendChild(iframe); // 获取数据之后销毁这个iframe,释放内存;这也保证了安全(不被其余域frame js访问) function destoryFrame() { iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); } }; // 请求跨域b页面数据 proxy('http://www.domain2.com/b.html', function(data){ alert(data); });
2.)proxy.html:(http://www.domain1.com/proxy....
中间代理页,与a.html同域,内容为空便可。
3.)b.html:(http://www.domain2.com/b.html)
<script> window.name = 'This is domain2 data!'; </script>
总结:经过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操做。
本质是建立本地服务器,由本地服务器请求跨域服务器上的数据,服务器之间没有同源限制,
在被请求的文件中添加一个header(IE10如下不支持)
<?php header("Access-Control-Allow-Origin:*"); ?>
缺点:只能实现get一种请求。
优势:简单适用,老式浏览器所有支持,服务器改造很是小。
原理:网页经过添加一个<script>
元素,向服务器请求JSON数据,这种作法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
//js function addScriptTag(src) { var script = document.createElement('script'); script.setAttribute("type","text/javascript"); script.src = src; document.body.appendChild(script); } window.onload = function () { addScriptTag('http://example.com/ip?callback=foo'); } function foo(data) { console.log('Your public IP address is: ' + data.ip); }; //服务端 <?php $callback = $_GET['foo']//获得回调函数名 $data = array('a','b','c'); //要返回的数据 echo $callback.'('.json_encode($data).')';//输出 ?>
jquery会自动生成一个全局函数来替换callback=?中的问号,以后获取到数据后又会自动销毁,实际上就是起一个临时代理函数的做用。
$.get('http://example.com/data.php?callback',function(){ alert('123'); },'jsonp'); <?php $callback = $_GET['callback']//获得回调函数名 $data = array('a','b','c'); //要返回的数据 echo $callback.'('.json_encode($data).')';//输出 ?>
getJSON方法会自动判断是否跨域,不跨域的话,就调用普通的ajax方法;跨域的话,则会以异步加载js文件的形式来调用jsonp的回调函数。
$.getJson('http://example.com/data.php?callback=?',function(){ alert('123'); }); <?php $callback = $_GET['callback']//获得回调函数名 $data = array('a','b','c'); //要返回的数据 echo $callback.'('.json_encode($data).')';//输出 ?>
以上作法还有一种解释就是JQuery把jsonp的实现封装在了ajax中
$.ajax({ type: "GET", url: "http://www.b.com/jsonp.js" dataType: "jsonp", jsonp:"callback",//“callback”:任意名字均可以 success: function(data) { //对data的处理 }, error: function(jqXHR){ alert("发生错误:" + jqXHR.status); }, }); //服务端的修改 $jsonp = $_GET["callback"];//callback名字和请求的名字相同
-->ajax是什么?经过在后台与服务器进行少许数据交换,AJAX 可使网页实现异步更新。这意味着能够在不从新加载整个网页的状况下,对网页的某部分进行更新。
//vue js this.$http.jsonp('http://www.domain2.com:8080/login', { params: {}, jsonp: 'onBack' }).then((res) => { console.log(res); }) //node.js后端代码 var querystring = require('querystring'); var http = require('http'); var server = http.createServer(); server.on('request', function(req, res) { var params = qs.parse(req.url.split('?')[1]); var fn = params.callback; // jsonp返回设置 res.writeHead(200, { 'Content-Type': 'text/javascript' }); res.write(fn + '(' + JSON.stringify(params) + ')'); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080...');
WebSocket是HTML5新的一种通讯协议,使用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
CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。相比JSONP只能发GET请求,CORS容许任何类型的请求。IE浏览器不能低于IE10。
整个CORS通讯过程,都是浏览器自动完成,不须要用户参与。对于开发者来讲,CORS通讯与同源的AJAX通讯没有差异,代码彻底同样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感受。
所以,实现CORS通讯的关键是服务器。只要服务器实现了CORS接口,就能够跨源通讯。
只要同时知足如下两大条件,就属于简单请求。
(1) 请求方法是如下三种方法之一:
HEAD
GET
POST
(2)HTTP的头信息不超出如下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
对于简单请求,浏览器直接发出CORS请求。具体来讲,就是在头信息之中,增长一个Origin字段。
下面是一个例子,浏览器发现此次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。
GET /cors HTTP/1.1 Origin: http://api.bob.com Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...
若是Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。若是Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。
Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: FooBar Content-Type: text/html; charset=utf-8
非简单请求是那种对服务器有特殊要求的请求,好比请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
非简单请求的CORS请求,会在正式通讯以前,增长一次HTTP查询请求,称为"预检"请求(preflight)。"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。"预检"请求的HTTP头信息:
Origin: 请求域名 Access-Control-Request-Method: 请求自身的方法 Access-Control-Request-Headers: 自定义的头部信息
发送请求后,服务器能够决定是否容许这种类型的请求,服务器经过在响应中发送以下头部与浏览器进行沟通
Access-Control-Allow-Origin: 相同的源信息,能够是* Access-Control-Allow-Methods: 容许的方法 Access-Control-Allow-Headers: 容许的头部 Access-Control-Max-Age:应该讲这个preflight缓存多久
preflight请求结束后,结果就按照响应中指定的时间缓存起来,而为此付出的代价只是第一次发送请求的时候会多发送一次HTTP请求。
CORS请求默认不发送Cookie和HTTP认证信息。若是要把Cookie发到服务器,一方面要服务器赞成,指定Access-Control-Allow-Credentials字段。
Access-Control-Allow-Credentials: true
另外一方面,开发者必须在AJAX请求中打开withCredentials属性。
var xhr = new XMLHttpRequest(); xhr.withCredentials = true;
前端XMLHttpRequest请求代码
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDR兼容 xhr.withCredentials = true;// 前端设置是否带cookie xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { alert(xhr.responseText); } }; xhr.open('post', 'http://www.domain2.com:8080/login', true); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.send();