说到跨域,首先要理解什么是跨域,浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域。
一.跨域基础知识
http://www.a.com:8080/a.html
http://为协议,www为子域名,a.com为主域名,8080为端口,a.html为资源地址,当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了,浏览器认为这不安全,所以拦截了响应。知道了什么是跨域,那么我们先要本地搭建个服务器,用wampserver或者xampp都可以,这里我用的是xampp,详情点击这里
二.跨域方案
1.jsonp
原理:利用 script 标签没有跨域限制的漏洞,例如 <script src=“http:///www.jsonp.com/test.php?callback=test” /script>前端会先定义一个test函数,后端会获取到callback后面这个test函数名,然后把前端需要的参数放到test函数里面并执行
准备两个文件(index.html,test.php),两个域名(www.test.com,www.jsonp.com)
index.html 放在www.test.com域名下,test.php放在www.jsonp.com域名下!
下面我们来看下简单的例子,,用ajax来测试一下
http://www.test.com/index.html
$.ajax({
type: "GET",
url: "http://www.jsonp.com/test.php",
dataType: "jsonp",
jsonp: "callback",
jsonpCallback: "test",
success: function (data) {
console.log(typeof data, data);
//object,{name: "zhangsan", age: 18}
console.log(`my name is ${data.name},my age is ${data.age}`);
//my name is zhangsan,my age is 18
}
});
http://www.jsonp.com/test.php
<?php
$data = array(
'name' => 'zhangsan',
'age' => 18,
);
$callback = $_GET['callback'];
echo $callback."(".json_encode($data).")";
?>
当dataType为jsonp,ajax默认会创建一个script标签,并把jsonp和 jsonpCallback参数拼接到script标签中src,然后并把script标签插入到head中
let script = document.createElement('script');
script.src = 'http://www.jsonp.com/test.php?callback=test';
script.type="text/javascript";
document.getElementsByTagName('head')[0].appendChild(script)
看下图:域名www.test.com去请求www.domain.com的数据,明显跨域
jsonp缺点:
只能发送get请求,不支持post put delete等方式
不安全,去请求别的域,得到别的域返回的数据,万一是个脚本,那就JJ了。
2.location.hash+iframe
原理:利用hash值进行通讯,例如:有三个页面a.html,b.html,c.html, a.html和c.html是同域的,一开始a.html给b.html传一个hash值,然后b.html收到hash值后,再把hash值传递给c.html,最后c.html将传过来的值传到a.html中(a.html和c.html是同域的)。
准备3个文件(a.html,b.html,chtml) 两个域名(www.test.com,www.hash.com)
a.html,c.html放在www.hash.com域名下,b.html放在www.test.com域名下
www.hash.com和www.test.com是不同域的!下面看a,b,c三个页面分别做了什么?
http://www.hash.com/a.html
<body>
<iframe id="iframe" src="http://www.test.com/b.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
// 向b.html传hash值
iframe.onload = function () {
iframe.src = iframe.src + '#我是a页面传过来的值';
};
// 给c.html的回调方法
function onCallback(res) {
console.log(decodeURIComponent(res).slice(1));
}
</script>
</body>
http://www.test.com/b.html
<body>
<iframe id="iframe" src="http://www.hash.com/c.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
// 监听a.html传来的hash值,再传给c.html
window.onhashchange = function () {
//输出a页面传过来的值
console.log(decodeURIComponent(location.hash).slice(1));
//b页面需要传给a页面的值,先传给c页面
iframe.src = iframe.src + "#我是b页面传过来的值";
};
</script>
</body>
http://www.hash.com/c.html
<body>
<script>
// 监听b.html传来的hash值
window.onhashchange = function () {
// 再通过操作同域a.html的js回调,将结果传回
window.parent.parent.onCallback(location.hash);
};
</script>
</body>
访问a会加载b,通过b的iframe的hash传值,然后b载入后,会加载c.html,并通过c的iframe的hash传值,而a和c是同域的,这样a就得到了c的值,所以就间接实现了a和b不同域的传值!
3.document.domain+iframe
原理:利用document.domain设置成自身或更高一级的父域,两者主域相同了就可以互通数据了(限制条件:主域必须相同才可以使用这种方式跨域)
准备两个文件a.html,b.html 主域相同子域不同的两个域名(a.domain.com,b.domain.com)
a.html放到a.domain.com域名下,b.html 放到b.domain.com域名下!
http://a.domain.com/a.html
<body>
<iframe src="http://b.domain.com/b.html" id="iframe" style="display:none;"></iframe>
<script>
document.domain = "domain.com";
var iframe = document.getElementById('iframe');
iframe.onload = function () {
console.log(iframe.contentWindow.value);
}
</script>
</body>
http://b.domain.com/b.html
<body>
<script>
document.domain = "domain.com";
var value = 100;
</script>
</body>
当a文件加载b文件的后,可以获取到b文件里面的数据,当主域之间想相互通信的时候,只要将两者的document.domain赋值为当前的主域名,即可实现跨域通信。
4.window.name+iframe
原理: window.name一直会存在当前窗口中,即时有新页面载入,window.name也不会变化,利用这点可以实现不同域名之前的跨域(需要一个代理页面作中转)
准备3个文件(a.html,b.html,proxy.html), 两个域名(www.test.com,www.name.com)
a.html,proxy放到www.test.com域名下,b.html放到www.name.com域名下
http://www.name.com/a.html
<body>
<iframe id="iframe" src="http://www.name.com/b.html" style="display: none;"></iframe>
<script>
let iframe = document.getElementById('iframe'),falg = true;
//这方法会执行两次,第一次是加载 src="http://www.name.com/b.html" 第二次加载是src = "proxy.html",第二次加载可以拿到第一次加载的winodw.name
iframe.onload = () => falg ? (iframe.src = "proxy.html", falg = false) : console.log(iframe.contentWindow.name);
</script>
</body>
http://www.test.com/b.html
<body>
<script>
window.name = "b页面传过来的window.name";
</script>
</body>
a.html加载iframe时候就会去加载b.html,b.html设置window.name,然后又会去加载c.html,那么c.html就能拿到这个winodw.name, 因为a.html,c.html同域,所以可以互通数据!window.name可以存储不超过2M的数据,传递的数据都会变成string类型。
5.postMessage+iframe
介绍:postMessage是html为了解决跨域通信,特别引入的一个新的API,用来解决跨页面通信,或者通过iframe嵌套的不同页面的通信的。postMessage为发送方,onmessage为接收方。
接受方:
otherWindow.postMessage(message, targetOrigin);
otherWindow为接收方的window对象,window.open()方法返回的值就是打开页面的window对象,frame元素的contentWindow属性能获得iframe标签内页面的window对象。
messag为所发送的内容,可以是字符串也可以是任何javascript对象。
targetOrigin是接收方域,可指定该值(一般为接收方页面的window.origin),如果接收方窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送。该参数也可以是‘*’,表示对接收方的域没有任何限制。
发送方:
window.addEventListener(“message”, function(e){},false);
函数参数e为message实例,里面包含了data、origin、source等属性,data是发送方发送的message,origin是发送方所属的域,source是发送方的window对象的引用。
第三个参数默认是false,冒泡阶段执行,如果为true是捕获阶段执行。
也可以写成 window.onmessage = function(e){ }
准备两个文件a.html,message.html,两个域名(www.test.com,www.postmessage.com)
a.html放到www.postmessage.com域名下,message.html放到www.test.com域名下
http://www.postmessage.com/a.html
<body>
<input type="button" value="点击发送数据" onclick="sendData();" />
<iframe src="http://www.test.com/message.html" id="iframe"></iframe>
<script>
let sendData = () => {
let iframe = document.getElementById('iframe'),
str = "我是发送的数据";
iframe.contentWindow.postMessage(str, 'http://www.test.com/message.html');
};
//监听message事件
window.addEventListener('message', () => {
if (event.origin !== 'http://www.test.com') return;
console.log(event.data);
}, false);
</script>
</body>
http://www.test.com/message.html
<body onload="receiveData();">
<p>message</p>
<script>
function receiveData() {
//监听message事件
window.addEventListener("message", (ev) => {
if (ev.origin != "http://www.postmessage.com") {
console.log("the event doesn't come from Domain1!");
return;
}
console.log(ev.data);
event.source.postMessage('我是返回的数据', event.origin);
});
};
</script>
</body>
还有2种跨域方式cors和webSocket,到时候我再补上!