一、前端跨域产生的原因?
导致跨域的根本原因是请求浏览器的同源策略导致的
浏览器有同源策略限制,协议、域名、端口号三者一样就是同源,三者只要有一个不同就是跨域。
二、什么是同源策略?
同源策略(same origin policy)是netScape(网景)提出的一个安全策略,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。具体表现为浏览器在执行脚本前,会判断脚本是否与打开的网页是同源的,判断协议、域名、端口是否都相同,相同则表示同源。其中一项不相同就表示跨域访问。会在控制台报一个CORS异常,目的是为了保护本地数据不被JavaScript代码获取回来的数据污染,因此拦截的是客户端发出的请求回来的数据接收,即请求发送了,服务器响应了,但是无法被浏览器接收。
浏览器采用同源策略,在没有明确授权的情况下,禁止页面加载或执行与自身不同源的任何脚本。
三、跨域常见的几种方式有哪些?
目录
四、具体解决跨域的实现实例?
1、JSONP (不常用)
原理:是利用<script>标签不存在跨域请求的限制
缺点:1、只能处理get请求 2、通过URl携带参数容易被劫持,不安全
举例:也可以动态的去创建<script>标签,原理在这随意发挥,只要是<script>标签即可
文件1:index.html
<script src="./index.jsonp.js"></script>
文件2:index.jsonp.js
$.ajax({
url: 'http://127.0.0.1:8001/list',
method: 'get',
dataType: 'jsonp', //=>执行的是JSONP的请求
success: res => {
console.log(res);
}
}); // 后端接受请求参数,并返回客户端,客服端拿到数据后执行函数
2、CORS 跨域资源共享 (前端不需要做任何改变)
原理:后端设置可访问的请求源
缺点:1、设置具体地址,有局限性 2、设置多源(*)就不能允许携带cookie了
举例:
前端发送请求(axios是基于promise封装)
axios.get('http://127.0.0.1:3001/user/list')
.then(result => {
console.log(result);
})
后端设置相关请求: *号为多源,也可以替换为具体地址
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Credentials", true);
res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length,Authorization, Accept,X-Requested-With");
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,HEAD,OPTIONS");
if (req.method === 'OPTIONS') {
res.send('OK!');
return;
}
next();
});
3、proxy反向代理 (常用)
非脚手架搭建,则修改webpack.config.js项目。若是直接用脚手架搭建,则修改vue.config.js
module.exports = {
entry: {},
module: {},
...
devServer: {
historyApiFallback: true,
proxy: [{
context: '/login',
target: 'http://www.domain2.com:8080', // 代理跨域目标接口
changeOrigin: true,
secure: false, // 当代理某些https服务报错时用
cookieDomainRewrite: 'www.domain1.com' // 可以为false,表示不修改
}],
noInfo: true
}
}
4、Nginx 代理(前端配置好,产品化去处理)
通过请求到Nginx,让Nginx将这个请求发送到真正的服务器
推荐:Nginx代理功能与负载均衡详解https://www.cnblogs.com/knowledgesea/p/5199046.html
# 工做进程的数量
worker_processes 1;
events {
worker_connections 1024; # 每一个工做进程链接数
}
http {
include mime.types;
default_type application/octet-stream;
# 日志格式
log_format access '$remote_addr - $remote_user [$time_local] $host "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" "$clientip"';
access_log /srv/log/nginx/access.log access; # 日志输出目录
gzip on;
sendfile on;
# 连接超时时间,自动断开
keepalive_timeout 60;
# 虚拟主机
server {
listen 8080;
server_name localhost; # 浏览器访问域名
charset utf-8;
access_log logs/localhost.access.log access;
# 路由
location / {
root www; # 访问根目录
index index.html index.htm; # 入口文件
}
}
# 引入其余的配置文件
include servers/*;
}
5、postMessage跨域消息传递
postMessage(data,origin)方法接受两个参数:
data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。
<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传送跨域数据-postMessage
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
};
// 接受domain2返回数据-addEventListener
window.addEventListener('message', function(e) {
alert('data from domain2 ---> ' + e.data);
}, false);
</script>
<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一种新的协议。不同于Http协议,它实现了浏览器与服务器全双工通信,同时允许跨域通讯,能够支持服务器和客户端双方都主动推送消息给对方。
原生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>
后端代码
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、document.domain + iframe
定义:该方案适用于:主域相同,子域名不同的情况。对于一些有产品体系的大公司而言,不同的页面可能在不同的服务器上,这些服务器的域名不同,但拥有同样的上级域名。如www.qq.com, im.qq.com, user.qq.com,这些服务器上的页面就可以用domain来实现跨域数据访问
使用方式
通过js将两个页面都强制设定同一个基础主域,来实现同域的效果
// 主窗口
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
document.domain = 'domain.com';
var user = 'admin';
</script>
// 子窗口
<script>
document.domain = 'domain.com';
// 获取父窗口中变量
console.log('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页面所有对象。
// 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>
// 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>
// 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值(2MB)
var proxy = function(url, callback) {
var state = 0;
var iframe = document.createElement('iframe');
// 加载跨域的页面,跨域页面会将数据写入name中
iframe.src = url;
// onload事件会触发2次,第1次加载跨域页,并留存数据于window.name,第二次则是通过同源网页拿到name
iframe.onload = function() {
if (state === 1) {
// 第2次onload(同域proxy页)成功后,读取同域window.name中数据
callback(iframe.contentWindow.name);
destoryFrame();
} else if (state === 0) {
// 第1次onload(跨域页)成功后,切换到同域代理页面
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);
});