同源策略请求
- 同源策略是
浏览器的安全策略,不是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>
修改浏览器的安全设置
跨域是因为浏览器的安全考虑,那就直接去改浏览器的设置啦。