什么是跨域?跨域产生原因与解决方法
一、什么是跨域?
当一个请求url的协议、域名、端口三者之间的任意一个与当前页面url不同即为跨域。
当前页面url | 被请求页面url | 是否跨域 | 原因 |
---|---|---|---|
http://www.test.com/ | http://www.test.com/index.html | 否 | 同源(协议、域名、端口号相同) |
http://www.test.com/ | https://www.test.com/index.html | 跨域 | 协议不同(http/https) |
http://www.test.com/ | http://www.baidu.com/ | 跨域 | 主域名不同(test/baidu) |
http://www.test.com/ | http://blog.test.com/ | 跨域 | 子域名不同(www/blog) |
http://www.test.com:8080/ | http://www.test.com:7001/ | 跨域 | 端口号不同(8080/7001) |
二、为什么会出现跨域?
出于浏览器的*同源策略限制**。
同源策略(Same Orgin Policy)是一种约定,它是浏览器核心也最基本的安全功能,它会阻止一个域的js脚本和另外一个域的内容进行交互,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。。所谓同源(即在同一个域)就是两个页面具有相同的协议(protocol)、主机(host)和端口号(port)。
三、非同源限制
- 无法读取非同源网页的cookie、localstorage和indexedDB
- 无法接触非同源网页的DOM和js对象
- 无法向非同源地址发送Ajax请求
四、跨域解决方法
(1)document.domain + iframe跨域
(2)location.hash + iframe跨域
(3)window.name + iframe跨域
(4)postMessage跨域
(5)通过jsonp跨域
(6)跨域资源共享(CORS)
(7)Nginx反向代理
(8)nodejs中间件地代理跨域
(9)WebSocket协议跨域
一般来说,前三个是iframe的跨域;窗口之间JS跨域postMessage;简单的跨域请求jsonp即可,复杂的用cors;开发环境下接口跨域用nginx反向代理或node中间件比较方便。
4.1、document.domain + iframe跨域
该方法仅限于主域相同,子域不同的跨域应用场景。
实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
(1)父窗口: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>
(2)子窗口:http://child.domain.com/b.html
<script>
document.domain = 'domain.com';
// 获取父窗口中变量
alert('get js data from parent ---> ' + window.parent.user);
</script>
4.2、location.hash + iframe跨域
实现场景:A域(a.html)-> B域(b.html)-> A域(c.html)。a与b不同域只能单向通信,但c与a同域,所以c可以通过parent.parent访问a页面所有对象。
实现原理:三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。
(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');
// 向b.html传hash值
setTimeout(function() {
iframe.src = iframe.src + '#user=admin';
}, 1000);
// 开放给同域c.html的回调方法
function onCallback(res) {
alert('data from c.html ---> ' + res);
}
</script>
(2)b.html:http://www.domain2.com/b.html
<iframe id="iframe" src="http://www.domain1.com/c.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
// 监听a.html传来的hash值,再传给c.html
window.onhashchange = function () {
iframe.src = iframe.src + location.hash;
};
</script>
(3)c.html:(http://www.domain1.com/c.html)
<script>
// 监听b.html传来的hash值
window.onhashchange = function () {
// 再通过操作同域a.html的js回调,将结果传回
window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));
};
</script>
4.3、window.name + iframe跨域
实现原理:通过iframe的src属性由外域转向本地域,跨域数据即由if让么的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时又是安全操作。
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>
4.4、postMessage跨域
实现原理:postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可以用于解决以下方面的问题:
- 页面和其打开的新窗口的数据传递
- 多窗口之间的消息传递
- 页面与嵌套的iframe消息传递
- 上面三个场景的跨域数据传递
用法:postMessage(data,origin)方法接受两个参数
- data:html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringfy()序列化。
- 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);
console.log(e.source); // e.source 发送消息的窗口
console.log(e.origin); // e.origin 消息发向的网址
console.log(e.data); // 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>
4.5、通过jsonp跨域
实现原理:通常为了减轻web服务器的负载,我们把js、css、图片等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许。我们可以利用