文章目录
一、跨域定义
1.1 跨域
当协议、域名、端口号,有一个或多个不同时,有希望可以访问并获取数据的现象称为跨域访问,同源策略限制下 cookie、localStorage、dom、ajax、IndexDB 都是不支持跨域的,
原因:
1.假设 cookie 支持了跨域,http 协议无状态,当用户访问了一个银行网站登录后,银行网站的服务器给返回了一个 sessionId,当通过当前浏览器再访问一个恶意网站,如果 cookie 支持跨域,恶意网站将获取 sessionId 并访问银行网站,出现安全性问题。
2.IndexDB、localStorage 等数据存储在不同域的页面切换时是获取不到的。
3.假设 dom 元素可以跨域,在自己的页面写入一个 iframe 内部嵌入的地址是 www.baidu.com,当在百度页面登录账号密码时就可以在自己的页面获取百度的数据信息,这显然是不合理的。
所以同源策略是为了安全性考虑而产生的,同源策略限制下,可以访问到后台服务器的数据,后台服务器会正常返回数据,而被浏览器给拦截了。
1.2 同源策略
同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
1.3、常见的跨域场景
二、实现跨域的方式
2.1 使用jsonp跨域
原理:
通常为了减轻web服务器的负载,我们把js、css,img等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许,基于此原理,我们可以通过动态创建script,再请求一个带参网址实现跨域通信。
使用场景:
自己的项目前端资源和后端部署在不同的服务器地址上,或者其他的公司需要访问自己对外公开的接口,需要实现跨域获取数据,如百度搜索。
优点:
(1)兼容低版本浏览器
(2)简单
缺点:
(1)只能发送 get 请求;
(2)不安全,容易引发 xss 攻击;
(3)要后端配合。
- 原生实现:
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参并指定回调执行函数为onBack
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=onBack';
document.head.appendChild(script);
// 回调执行函数
function onBack(res) {
alert(JSON.stringify(res));
}
</script>
- jquery ajax:
$.ajax({
url: 'http://www.domain2.com:8080/login',
type: 'get',
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: "onBack", // 自定义回调函数名
data: {}
});
- vue.js:
this.$http.jsonp('http://www.domain2.com:8080/login', {
params: {},
jsonp: 'onBack'
}).then((res) => {
console.log(res);
})
后端node.js代码示例:
var querystring = require('querystring');
var http = require('http');
var server = http.createServer();
server.on('request', function(req, res) {
var params = qs.parse(req.url.split('?')[1]);
var fn = params.callback;
// jsonp返回设置
res.writeHead(200, { 'Content-Type': 'text/javascript' });
res.write(fn + '(' + JSON.stringify(params) + ')');
res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
2.2 使用CORS跨域
原理:
是 W3C 的一个工作草案,定义了在必须访问跨源资源时,浏览器与服务器应该如何沟通。CORS 背后的基本思想,就是使用自定义的 HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。
使用场景:
多用于开发时,前端与后台在不同的 ip 地址下进行数据访问。
普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置。
带cookie请求:前后端都需要设置字段,另外需注意:所带cookie为跨域请求接口所在域的cookie,而非当前页。
目前,所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)),CORS也已经成为主流的跨域解决方案。
优点:
(1)支持多种请求
(2)可进行DOM查询
缺点:
(1)早期浏览器不兼容
(2)前后端配合
- 前端设置
1)原生ajax
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
// 前端设置是否带cookie
xhr.withCredentials = true;
xhr.open('post', 'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText);
}
};
2)jQuery ajax
$.ajax({
...
xhrFields: {
withCredentials: true // 前端设置是否带cookie
},
crossDomain: true, // 会让请求头中包含跨域的额外信息,但不会含cookie
...
});
3)vue框架在vue-resource封装的ajax组件中加入以下代码:
Vue.http.options.credentials = true
- 服务器后端
1)Java后台
/*
* 导入包:import javax.servlet.http.HttpServletResponse;
* 接口参数中定义:HttpServletResponse response
*/
response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com"); // 若有端口需写全(协议+域名+端口)
response.setHeader("Access-Control-Allow-Credentials", "true");
2)Nodejs后台示例:
var http = require('http');
var server = http.createServer();
var qs = require('querystring');
server.on('request', function(req, res) {
var postData = '';
// 数据块接收中
req.addListener('data', function(chunk) {
postData += chunk;
});
// 数据接收完毕
req.addListener('end', function() {
postData = qs.parse(postData);
// 跨域后台设置
res.writeHead(200, {
'Access-Control-Allow-Credentials': 'true', // 后端允许发送Cookie
'Access-Control-Allow-Origin': 'http://www.domain1.com', // 允许访问的域(协议+域名+端口)
'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly:脚本无法读取cookie
});
res.write(JSON.stringify(postData));
res.end();
});
});
server.listen('8080');
console.log('Server is running at port 8080...');
2.3 使用 postMessage 实现跨域
原理:
postMessage 是 H5 的新 API,跨文档消息传送(cross-document messaging),有时候简称为 XMD,指的是在来自不同域的页面间传递消息。
调用方式:window.postMessage(message, targetOrigin)
- message:发送的数据
- targetOrigin:发送的窗口的域
在对应的页面中用 message 事件接收,事件对象中有 data、origin、source 三个重要信息
- data:接收到的数据
- origin:接收到数据源的域(数据来自哪个域)
- source:接收到数据源的窗口对象(数据来自哪个窗口对象)
使用场景:
不是使用 Ajax 的数据通信,更多是在两个页面之间的通信,在 A 页面中引入 B 页面,在 A、B 两个页面之间通信。
优点:
使用简单
缺点:
早期浏览器不支持
1)a.html:(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>
2)b.html:(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>
2.4 使用 window.name + iframe实现跨域
原理:
name值在不同的页面(甚至不同域名)加载后依旧存在,通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作(即设置中间页面)。
优点:
不需要后端配合,简单
缺点:
有大小限制name值只有2M大小
2.5 使用 location.hash + iframe实现跨域
原理
与name值一样,通过中间页来实现
A域:a.html -> B域:b.html -> A域:c.html,a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象。
2.6 使用 document.domain + iframe实现跨域
使用场景:
不是万能的跨域方式,大多使用于同一公司不同产品间获取数据,必须是一级域名和二级域名的关系,如 www.baidu.com 与 video.baidu.com 之间。
实现方式:
两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
1.)父窗口:(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>
2.)子窗口:(child.domain.com/b.html)
<script>
document.domain = 'domain.com';
// 获取父窗口中变量
alert('get js data from parent ---> ' + window.parent.user);
</script>
2.7 使用 WebSocket 实现跨域
WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
1.)前端代码:
<div>user input:<input type="text"></div>
<script src="./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>
2.)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.');
});
});
2.8 使用 nginx 实现跨域
nginx 本身就是一个服务器,因此我们需要去 nginx 官网下载服务环境 http://nginx.org/en/download.html。
- 下载后解压到一个文件夹中
- 双击 nginx.exe 启动(此时可以通过 http://localhost 访问 nginx 服务)
- 在目录新建 json 文件夹
- 进入 json 文件夹新建 data.json 文件并写入内容
- 回到 nginx 根目录进入 conf文件夹
- 使用编辑器打开 nginx.conf 进行配置
原理:
同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。
实现:
通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。
2.9 使用 NodeJS 中间件http-proxy-middleware 实现跨域
原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发。
三、如何选择
- 根据不同方法的优缺点来分析
- 有一种场景是上传到服务器上接口和网站是同一个域名就不存在跨域的问题,但需要本地开发时会有跨域问题,选择CORS方法中的普通跨域,即安装chrome浏览器的cors插件。
四、参考文章
https://www.pandashen.com
https://blog.csdn.net/BulengCY/article/details/80495443
https://blog.csdn.net/weixin_37569048/article/details/80484150