跨域及其解决方案
同源策略请求
- 同源策略是
浏览器的安全策略,不是HTTP协议的一部分。所以 服务器端 调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。 - 同源是指
协议+域名+端口号三者相同,只要有一个不一样,就会发生跨域 - ajax / fetch 做请求的时候去调用接口url,可能就会产生跨域
1、JSONP
script、img、link、iframe标签不存在跨域请求限制- 通过
script标签去调用跨域的接口,把参数和回调函数名(需要是全局函数)传给服务器,服务器接收客户端请求,给客户端返回 回调函数 和 数据的字符串,浏览器拿到字符串进行解析,然后执行函数。 - 缺点:只支持
GET请求、需要服务端支持、安全性不好 - 样例:
后端接口:
// 处理成功失败返回格式的工具
const {successBody} = require('../utli')
class CrossDomain {
static async jsonp (ctx) {
// 前端传过来的参数
const query = ctx.request.query
// 设置一个cookies
ctx.cookies.set('tokenId', '1')
// query.cb是前后端约定的方法名字,其实就是后端返回一个直接执行的方法给前端
// 由于前端是用script标签发起的请求,所以返回了这个方法后相当于立马执行,并且把要返回的数据放在方法的参数里。
ctx.body = `${query.cb}(${JSON.stringify(successBody({msg: query.msg}, 'success'))})`
}
// 因为返回的数据是字符串,所以这里要写stringify使浏览器解析完后能执行把数据转成json格式
}
module.exports = CrossDomain
简单版前端:
// 后端返回直接执行的方法,相当于执行这个方法,由于后端把返回的数据放在方法的参数里,所以这里能拿到res。
window.jsonpCb = function (res) { console.log(res) }
<script src='http://localhost:9871/api/jsonp?msg=helloJsonp&cb=jsonpCb'></script>
2、CORS跨域资源共享
- 普通跨域请求:只服务端设置 ~Access-Control-Allow-Origin` 即可,前端无须设置
- 若要带
cookie请求:前后端都需要设置。xhr.withCredentials = true; // 前端设置是否带cookie - 所有的跨域请求会有 两个请求,浏览器会预先发送
OPTIONS(试探性)请求,看是否可以和服务器建立跨域传输,再传真正的请求。
3、Nodejs中间件Proxy代理跨域
- node中间件 实现跨域代理,原理大致与nginx相同,都是通过一个
代理服务器,实现 数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。 - 配置
webpack和webpack-dev-server - 用node模拟了一个nginx服务请求,处理跨域请求

4、nginx反向代理
这个暂时还未去学习,先空着吧,大概就是有这么个东西
5、postMessage
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多 可以跨域操作的windowd属性* 之一,它可用于解决以下方面的跨域数据传递:- a) 页面和其打开的新窗口的数据传递
- b) 多窗口之间消息传递
- c) 页面与嵌套的iframe消息传递
- 用法:
postMessage(data,origin)方法接受 两个参数data:html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。(另一方在用JSON.parse()转回来)origin:协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。
- 样例
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>
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>
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>
Nodejs socket后台
var http = require('http');
var socket = require('socket.io');
// 启http服务
var server = http.createServer(function(req, res) {
res.writeHead(200, {
'Content-type': 'text/html'
});
res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
// 监听socket连接
socket.listen(server).on('connection', function(client) {
// 接收信息
client.on('message', function(msg) {
client.send('hello:' + msg); //给客户端发送信息
console.log('data from client: ---> ' + msg);
});
// 断开处理
client.on('disconnect', function() {
console.log('Client socket has closed.');
});
});
7、window.domain+iframe
- 缺点:仅限主域相同
domain.com,子域不同www和child的跨域应用场景 - 实现原理:把两个页面的
document.domain设置成 主域,就实现了同域。 - 样例
父窗口 如: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);
</script>
8、window.name+iframe
- window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。
- 总结:通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。
- 样例
- A页面

- proxy页面,不写东西(不写window.name):proxy页面和A是同域,且无关紧要,无内容
- B页面:B想给A的数据放在window.name中

- A页面
location.hash+iframe
- 缺点:hash有
大小限制(4kb左右),不能传复杂数据。 - 实现原理:A想和B跨域相互通信,通过中间页C来实现。 三个页面,不同域之间利用
iframe的location.hash传值,相同域之间直接js访问来通信。(PS:A和C同源,A和B不同源) - 具体实现:
A域:a.html -> B域:b.html -> A域:c.html
A给B信息直:A->B B给A信息:B->C->A
A与B不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象。 - 样例
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');
//1:向b.html传hash值
setTimeout(function() {
iframe.src = iframe.src + '#user=admin'; //其中'#user=admin'是hash值
}, 1000);
// 开放给同域c.html的回调方法
function onCallback(res) {
alert('data from c.html ---> ' + res);
}
</script>
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');
//2:监听a.html传来了hash值'#user=admin',hash值发生变化,再传给c.html
window.onhashchange = function () {
iframe.src = iframe.src + location.hash;
};
</script>
c.html:(http://www.domain1.com/c.html)
<script>
//3: 监听到b.html传来了hash值,hash变化了
window.onhashchange = function () {
// 再通过操作同域a.html的js回调,将结果传回
window.parent.parent.onCallback(location.hash.replace('#user=', ''));
};
</script>
修改浏览器的安全设置
跨域是因为浏览器的安全考虑,那就直接去改浏览器的设置啦。

浙公网安备 33010602011771号