前端解决跨域的几种方法
前端解决跨域的几种方法
引言
上文中提到了 cors 解决跨域的方法,除此之外还有很多。
一、JSONP 跨域
jsonp 的原理就是利用 <script>
标签没有跨域限制,通过<script>
标签 src 属性,发送带有 callback 参数的 GET 请求,服务端将接口返回数据拼凑到 callback 函数中,返回给浏览器,浏览器解析执行,从而前端拿到 callback 函数返回的数据。
JSONP 的缺点:
- 具有局限性, 仅支持 get 方法
- 不安全,可能会遭受 XSS 攻击
二、 iframe 跨域
2.1 window.name + iframe 跨域
window.name 属性的独特之处:name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持大小 2MB 的 name 值。
2.2 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 页面所有对象。
2.3 document.domain + iframe 跨域
此方案仅限主域相同,子域不同的跨域应用场景。实现原理:两个页面都通过 js 强制设置 document.domain 为基础主域,实现同域。
三、postMessage 跨域
postMessage 是 HTML5 XMLHttpRequest Level 2 中的 API,且是为数不多可以跨域操作的 window 属性之一,它可用于解决以下方面的问题的跨域:
- 页面和其打开的新窗口的数据传递
- 多窗口之间消息传递
- 页面与嵌套的 iframe 消息传递
用法:postMessage(data,origin) 方法接受两个参数:
- data: html5 规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用 JSON.stringify() 序列化。
- origin: 协议 + 主机 + 端口号,也可以设置为 "*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为 "/"。
四、nginx 反向代理跨域
Nginx 是一款轻量级的 Web 服务器,也可以用于反向代理、负载平衡和 HTTP 缓存等。Nginx 使用异步事件驱动的方法来处理请求,是一款面向性能设计的 HTTP 服务器。通过 Nginx 配置一个代理服务器域名与 domain1 相同,端口不同)做跳板机,反向代理访问 domain2 接口,并且可以顺便修改 cookie 中 domain 信息,方便当前域 cookie 写入,实现跨域访问。
五、nodejs 中间件代理跨域
通过启一个代理服务器,实现数据的转发,也可以通过设置 cookieDomainRewrite 参数修改响应头中 cookie 中域名,实现当前域的 cookie 写入,方便接口登录认证。
六、前端正向代理跨域
与反向代理跨域相对,在客户端设置了一个代理服务器,并且指定目标服务器,之后代理服务器向目标服务器转交请求并将获得的内容发送给客户端。本质上起到了对服务器隐藏客户端的目的。一般新建一个 setupProxy.js 进行配置
const proxy = require('http-proxy-middleware');
module.exports = function (app) {
app.use( proxy ('/api', {
target: url, /*这里写自己的代理地址*/
changeOrigin: true,
ws: true,
pathRewrite: {
'^/api': ''
},
}));
};
七、WebSocket 协议跨域
WebSocket protocol 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是 server push 技术的一种很好的实现。
// 前端代码
<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>
// 后台代码
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.');
});
});
八、通用方法(仅限公司内网使用)
1. 在 chrome://flags 里搜索'same-site', 将下图 ‘SameSite by default cookies’ 项设置为 disabled,重启 chrome 即可:
注意:浏览器的 Cookie 新增加了一个SameSite
属性,用来防止 CSRF 攻击和用户追踪。
它可以设置三个值:
- Strict
- Lax
- None
管控程度依次降低
2. 如果搜不到这个配置,说明当前 chrome 版本太高了,请参看下面的解决方案。
2.1 本地配置代理,将本地 ip 指向域名。(优点:不需要动 chrome;缺点:配置有成本,和环境有耦合)
2.2 下载低版本的 chrome,如 86.0.4240.75,下载地址。(优点:可以永久性避开问题;缺点:无法再体验新版 chrome 的功能)。安装后执行命令,禁止更新:
cd ~/Library/Google
sudo chown root:wheel GoogleSoftwareUpdate