一、概念
同源:域名、端口、协议均相同。
跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的
二、跨域解决方法
- 跨域资源共享(CORS)
- jsonp
- nginx代理跨域
- webpack设置代理proxy/nodejs中间件代理跨域
- postMessage跨域
- WebSocket协议跨域
- document.domain + iframe跨域
- location.hash + iframe跨域
- window.name + iframe跨域
以上9种常见的跨域解决方案,
CORS(支持所有类型的HTTP请求,但浏览器IE10以下不支持)适合做ajax各种跨域请求;
jsonp(只支持get请求,支持老的IE浏览器)适合加载不同域名的js、css,img等静态资源;
Nginx代理跨域和nodejs中间件跨域原理都相似,都是搭建一个服务器,直接在服务器端请求HTTP接口,这适合前后端分离的前端项目调后端接口。
postMessage、websocket都是HTML5新特性,兼容性不是很好,只适用于主流浏览器和IE10+。
document.domain+iframe适合主域名相同,子域名不同的跨域请求。
1、跨域资源共享(CORS)
所有的跨域请求(简单或非简单)总会包含一个origin的请求头部,由浏览器添加不受用户控制。值由协议、域名、端口组成,说明请求的来源。下面为一个Origin头部示例:
Origin: http://0.0.0.0:8000
服务器接受这个请求,会在响应头Access-Control-Allow-Origin回发相同的源信息。( * 表明该资源可以被任意外域访问)
Access-Control-Allow-Origin:http://0.0.0.0:8000
2、jsonp
jsonp的原理就是利用<script>标签没有跨域限制,通过<script>标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。
jsonp只能实现get一种请求。
原生实现:(各框架和异步请求插件都有对jsonp请求的请求方式的封装,照搬即可)
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
document.head.appendChild(script);
// 回调执行函数
function handleCallback(res) {
alert(JSON.stringify(res));
}
</script>
服务端返回如下(返回时即执行全局函数):
handleCallback({"status": true, "user": "admin"})
3、nginx代理跨域
- nginx配置解决iconfont跨域
- nginx反向代理接口跨域
4、 webpack设置代理proxy/nodejs中间件代理跨域
- 使用webpack搭建的项目,可以在webpack配置文件配置proxy代理
// 接口代理示例
proxy: {
"/boss": {
"target": "https://test-overseas.91dbq.com",
"changeOrigin": true, //如果跨域,需要配置这个参数
"secure":false, //如果是https请求,要配置这个参数
"pathRewrite": { "^/boss" : "/boss-gateway-sg/boss" }
}
},
- 原生可以使用中间件代理
// 中间件服务器
var express = require('express');
var proxy = require('http-proxy-middleware');
var app = express();
app.use('/', proxy({
// 代理跨域目标接口
target: 'https://test-overseas.91dbq.com',
changeOrigin: true,
// 修改响应头信息,实现跨域并允许带cookie
onProxyRes: function(proxyRes, req, res) {
res.header('Access-Control-Allow-Origin', 'http://0.0.0.0:8000');
res.header('Access-Control-Allow-Credentials', 'true');
},
// 修改响应信息中的cookie域名
cookieDomainRewrite: '0.0.0.0:8000' // 可以为false,表示不修改
}));
app.listen(3000);
console.log('Proxy server is listen at port 3000...');
5、postMessage跨域
6、WebSocket协议跨域
WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。
原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
<div>user input:<input type="text"></div>
<script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
<script>
var socket = io('http://www.domain2.com:8080');
// 连接成功处理
socket.on('connect', function() {
// 监听服务端消息
socket.on('message', function(msg) {
console.log('data from server: ---> ' + msg);
});
// 监听服务端关闭
socket.on('disconnect', function() {
console.log('Server socket has closed.');
});
});
document.getElementsByTagName('input')[0].onblur = function() {
socket.send(this.value);
};
</script>
7、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>
8、location.hash + iframe跨域
实现原理: a与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。
具体实现:A域:a.html -> B域:b.html -> A域:c.html,a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象。
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>
9、window.name + iframe跨域
window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。
1.)b.html:http://www.domain2.com/b.html
<script>
window.name = 'This is domain2 data!';
</script>
2.)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(跨域页)成功后,此时已拿到window.name,然后切换到同域代理页面
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); //This is domain2 data!
});
3.)proxy.html:http://www.domain1.com/proxy.html
中间代理页,与a.html同域,内容为空即可。
网友评论