九种跨域解决方案
何为跨域
想必大家都知道,不在赘述了,跨域问题出现的原因就是浏览器的安全机制:同协议、域名、端口。下面就总结下常用的几种解决方案。
1、JSONP(只能发送get请求,不支持post、put、delete;不安全xss攻击)
jsonp的详细介绍在另一篇文章,这里就以百度的查询接口做简单展示:
<script>
// 封装简单的jsonp
function jsonp(url, params, cb) {
return new Promise((resovle, reject) => {
let script = document.creatElement('script');
window[cb] = function(data) {
resovle(data);
document.body.removeChild(script);
}
params = {...params, cb}; //wd=b&cb=show
let arrs = [];
for(let key in params) {
arrs.push(`${key}=${params[key]}`);
}
script.url = `${url}?${arrs.join('&')}`;
document.body.appendChild(script);
})
}
// jsonp调用方式
jsonp({
url:'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su',
params:{wd: 'b'},
cb: show
}).then(data=>{
console.log(data)
})
</script>
2、cors(后台配置)
下面以express为例
// 设置那些原可以访问接口
res.setHeader('Access-Control-Allow-Origin', origin)
// 允许携带哪个头访问
res.setHeader('Access-Control-Allow-Headers', 'name')
// 允许那些请求方法
res.setHeader('Access-Control-Allow-Methods', 'PUT,POST,DELETE')
// 允许携带cookie
res.setHeader('Access-Control-Allow-Credentials',true)
// 预检测存活时间(options请求)
res.setHeader('Access-Control-Max-Age',6000)
// 允许前端获取哪个请求头(允许返回的头)
res.setHeader('Access-Control-Expose-Header','name')
3、iframe postMessage
postMessage 是 HTML5 XMLHttpRequest Level 2 中的 API,且是为数不多可以跨域操作的 window 属性之一,它可用于解决以下方面的问题:
- 页面和其打开的新窗口的数据传递
- 多窗口之间消息传递
- 页面与嵌套的 iframe 消息传递
- 上面三个场景的跨域数据传递
postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
otherWindow.postMessage(message, targetOrigin, [transfer]);
- message: 将要发送到其他 window 的数据。
- targetOrigin:通过窗口的 origin 属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个 URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配 targetOrigin 提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。
- transfer(可选):是一串和 message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。
接下来我们看个例子: http://localhost:3000/a.html页面向http://localhost:4000/b.html传递“我爱你”,然后后者传回"我不爱你"。
// a.html
<iframe src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"></iframe//等它加载完触发一个事件
//内嵌在http://localhost:3000/a.html
<script>
function load() {
let frame = document.getElementById('frame')
frame.contentWindow.postMessage('我爱你', 'http://localhost:4000') //发送数据
window.onmessage = function(e) { //接受返回数据
console.log(e.data) //我不爱你
}
}
</script>
// b.html
window.onmessage = function(e) {
console.log(e.data) //我爱你
e.source.postMessage('我不爱你', e.origin)
}
4、window.name
window.name 属性的独特之处:name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。
a、b页面同域,a页面嵌套c页面,onload事件第一次载入c页面url变为b页面并且获取contentWindow.name
// a.html(http://localhost:3000/b.html)
<iframe src="http://localhost:4000/c.html" frameborder="0" onload="load()" id="iframe"></iframe>
<script>
let first = true
// onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
function load() {
if(first){
// 第1次onload(跨域页)成功后,切换到同域代理页面
let iframe = document.getElementById('iframe');
iframe.src = 'http://localhost:3000/b.html';
first = false;
}else{
// 第2次onload(同域b.html页)成功后,读取同域window.name中数据
console.log(iframe.contentWindow.name);
}
}
</script>
// c.html(http://localhost:4000/c.html)
<script>
window.name = '我不爱你'
</script>
5、hash
a中嵌套c,c中嵌套b,a=>b=>c传递location.hash,a页面用window.onhashchange获取hash值
// a.html
<iframe src="http://localhost:4000/c.html#iloveyou"></iframe>
<script>
window.onhashchange = function () { //检测hash的变化
console.log(location.hash);
}
</script>
// b.html
<script>
window.parent.parent.location.hash = location.hash
//b.html将结果放到a.html的hash值中,b.html可通过parent.parent访问a.html页面
</script>
// c.html
console.log(location.hash);
let iframe = document.createElement('iframe');
iframe.src = 'http://localhost:3000/b.html#idontloveyou';
document.body.appendChild(iframe);
6、document.domain
必须是一级域名和二级域名的关系
// a.html
<body>
helloa
<iframe src="http://b.zf1.cn:3000/b.html" frameborder="0" onload="load()" id="frame"></iframe>
<script>
document.domain = 'zf1.cn'
function load() {
console.log(frame.contentWindow.a);
}
</script>
</body>
// b.html
<body>
hellob
<script>
document.domain = 'zf1.cn'
var a = 100;
</script>
</body>
7、websocket
可以参考阮大的文章。
// socket.html
<script>
let socket = new WebSocket('ws://localhost:3000');
socket.onopen = function () {
socket.send('我爱你');//向服务器发送数据
}
socket.onmessage = function (e) {
console.log(e.data);//接收服务器返回的数据
}
</script>
// server.js
let express = require('express');
let app = express();
let WebSocket = require('ws');//记得安装ws
let wss = new WebSocket.Server({port:3000});
wss.on('connection',function(ws) {
ws.on('message', function (data) {
console.log(data);
ws.send('我不爱你')
});
})
8、ngxin
实现原理类似于 Node 中间件代理,需要你搭建一个中转 nginx 服务器,用于转发请求。
使用 nginx 反向代理实现跨域,是最简单的跨域方式。只需要修改 nginx 的配置即可解决跨域问题,支持所有浏览器,支持 session,不需要修改任何代码,并且不会影响服务器性能。
实现思路:通过 nginx 配置一个代理服务器(域名与 domain1 相同,端口不同)做跳板机,反向代理访问 domain2 接口,并且可以顺便修改 cookie 中 domain 信息,方便当前域 cookie 写入,实现跨域登录。
先下载nginx,然后将 nginx 目录下的 nginx.conf 修改如下:
// proxy服务器
server {
listen 80;
server_name www.domain1.com;
location / {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
index index.html index.htm;
# 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为*
add_header Access-Control-Allow-Credentials true;
}
}
9、webpack proxy
//服务器启动目录;
devServer: {
contentBase: './dist',
hot: true,
// host:'1ocalhost',
port: 8586,
// compress:true,
//解决跨域
proxy: {
'/api': {
target: 'http://localhost:8087',
pathRewrite: { '^/api': '' },
changeOrigin: true,
secure: false, // 接受 运行在 https 上的服务
}
}
},