了解跨域出现的原因
什么是跨域?
跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。
这里说明一下,无法跨域是浏览器对于用户安全的考虑,如果自己写个没有同源策略的浏览器,完全不用考虑跨域问题了。
同源策略
同源策略,它是由Netscape提出的一个著名的安全策略。现在所有支持JavaScript 的浏览器都会使用这个策略。
所谓同源是指,域名,协议,端口相同。
当一个浏览器的两个tab页中分别打开百度和谷歌的页面,当浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的,即检查是否同源,只有和百度同源的脚本才会被执行。 如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。
同源策略是浏览器的行为,是为了保护本地数据不被JavaScript代码获取回来的数据污染,因此拦截的是客户端发出的请求回来的数据接收,即请求发送了,服务器响应了,但是无法被浏览器接收。
重点来了:浏览器的行为,那避开浏览器就好了
同源策略限制了一下行为:
- Cookie、LocalStorage 和 IndexDB 无法读取
- DOM 和 JS 对象无法获取
- Ajax请求发送不出去
说的更直白一点,就是我们在前端使用ajax发送异步请求,如果这个请求的URL地址与当前地址栏中的URL地址协议不同、域名不同、端口不同时,都成为跨域,例如:
<img src=“https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e1cc9a62920b4042bb6f4b4ffeef5b8a~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image),应用于JSON的一种新方法。
JSON、JSONP的区别:
- JSON返回的是一串数据,JSONP返回的是脚本代码(包含一个函数调用)
- JSONP只支持get请求、不支持post请求(类似往页面添加一个script标签,通过src属性去触发对指定地址的请求,故只能是Get请求)
JSONP应用的技巧:在HTML标签里,一些标签比如script、img这样的获取资源的标签是没有跨域限制的。
举例:1.原生实现:
<script>var script = document.createElement('script');script.type = 'text/javascript';
// 传参并指定回调执行函数为onBackscript.src = 'http://www.demo.com:8080/login?user=admin&callback=onBack';document.head.appendChild(script);
// 回调执行函数function onBack(res) {alert(JSON.stringify(res));}
</script>
2.jQuery Ajax:
$.ajax({url: 'http://www.demo.com:8080/login',type: 'get',dataType: 'jsonp',// 请求方式为jsonpjsonpCallback: "onBack",// 自定义回调函数名data: {}
});
3.Vue.js :
this.$http.jsonp('http://www.demo.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.跨域资源共享(CORS)
普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求,则前后端都需要设置。
需注意的是:由于同源策略的限制,所读取的cookie为跨域请求接口所在域的cookie,而非当前页。如果想实现当前页cookie的写入,可参考下文:nginx反向代理 和 NodeJs中间件代理
目前,所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)),CORS也已经成为主流的跨域解决方案。
后端修改请求头:> header(‘Access-Control-Allow-Origin:*’);允许访问的网址> header(‘Access-Control-Allow-Method:POST,GET’);允许访问的方式
3.nginx代理跨域
浏览器是禁止跨域的,但是服务端不禁止
nginx配置解决iconfont跨域
浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。
location / {add_header Access-Control-Allow-Origin *;
}
nginx反向代理接口跨域
跨域原理: 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。
实现思路:通过nginx配置一个代理服务器(域名与demo1相同,端口不同)做跳板机,反向代理访问demo2接口,并且可以顺便修改cookie中demo信息,方便当前域cookie写入,实现跨域登录。
nginx具体配置:
#代理服务器
server {listen 81;server_namewww.demo1.com;
location / {proxy_pass http://www.demo2.com:8080;#反向代理proxy_cookie_demo www.demo2.com www.demo1.com; #修改cookie里域名indexindex.html index.htm;
# 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用add_header Access-Control-Allow-Origin http://www.demo1.com;#当前端只跨域不带cookie时,可为*add_header Access-Control-Allow-Credentials true;}
}
前端代码示例:
var xhr = new XMLHttpRequest();
// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;
// 访问nginx中的代理服务器
xhr.open('get', 'http://www.demo1.com:81/?user=admin', true);
xhr.send();
Nodejs后台示例:
var http = require('http');
var server = http.createServer();
var qs = require('querystring');
server.on('request', function(req, res) {var params = qs.parse(req.url.substring(2));
// 向前台写cookieres.writeHead(200, {'Set-Cookie': 'l=a123456;Path=/;Domain=www.demo2.com;HttpOnly' // HttpOnly:脚本无法读取});
res.write(JSON.stringify(params));res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
4.postMessage跨域
简介: postMessage是h5引入的一个新概念,现在也在进一步的推广和发展中,他进行了一系列的封装,我们可以通过window.postMessage的方式进行使用,并可以监听其发送的消息;兼容性: 移动端可以放心用,但是pc端需要做降级处理优点: 不需要后端介入就可以做到跨域,一个函数外加俩个参数(请求url,发送数据)就可以搞定;移动端兼容性好;缺点: 无法做到一对一的传递方式:监听中需要做很多消息的识别,由于postMessage发出的消息对于同一个页面的不同功能相当于一个广播的过程,该页面的所有onmessage都会收到,所以需要做消息的判断;**安全性问题:**三方可以通过截获,注入html或者脚本的形式监听到消息,从而能够做到篡改的效果,所以在postMessage和onMessage中一定要做好这方面的限制;发送的数据会通过结构化克隆算法进行序列化,所以只有满足该算法要求的参数才能够被解析,否则会报错,如function就不能当作参数进行传递;
用法: postMessage(data,origin)方法接受两个参数> data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。> origin: 协议+主机+端口号,也可以设置为"*“,表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为”/"。
示例代码:
a.html:(www.demo1.com/a.html)
<iframe id="iframe" src="http://www.demo2.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.demo2.com');};
// 接受domain2返回数据window.addEventListener('message', function(e) {alert('data from demo2 ---> ' + e.data);}, false); </script>
b.html:(www.demo2.com/b.html)
<script> // 接收domain1的数据window.addEventListener('message', function(e) {alert('data from demo1 ---> ' + e.data);
var data = JSON.parse(e.data);if (data) {data.number = 16;
// 处理后再发回domain1window.parent.postMessage(JSON.stringify(data), 'http://www.demo1.com');}}, false); </script>
5.Nodejs中间件代理跨域
node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。
利用node + express + http-proxy-middleware搭建一个proxy服务器。(2次跨域)
前端代码示例:
var xhr = new XMLHttpRequest();
// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;
// 访问http-proxy-middleware代理服务器
xhr.open('get', 'http://www.demo1.com:3000/login?user=admin', true);
xhr.send();
代理服务器(中间件):
var express = require('express');
var proxy = require('http-proxy-middleware');
var app = express();
app.use('/', proxy({// 代理跨域目标接口target: 'http://www.demo2.com:8080',changeOrigin: true,
// 修改响应头信息,实现跨域并允许带cookieonProxyRes: function(proxyRes, req, res) {res.header('Access-Control-Allow-Origin', 'http://www.domain1.com');res.header('Access-Control-Allow-Credentials', 'true');},
// 修改响应信息中的cookie域名cookieDomainRewrite: 'www.demo1.com'// 可以为false,表示不修改
}));
app.listen(3000);
console.log('Proxy server is listen at port 3000...');
Nodejs后台:
var http = require('http');
var server = http.createServer();
var qs = require('querystring');
server.on('request', function(req, res) {var params = qs.parse(req.url.substring(2));
// 向前台写cookieres.writeHead(200, {'Set-Cookie': 'l=a123456;Path=/;Domain=www.demo2.com;HttpOnly' // HttpOnly:脚本无法读取});
res.write(JSON.stringify(params));res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
vue框架的跨域(1次跨域)
利用node + webpack + webpack-dev-server代理接口跨域。在开发环境下,由于vue渲染服务和接口代理服务都是webpack-dev-server同一个,所以页面与代理接口之间不再跨域,无须设置headers跨域信息了。
webpack.config.js部分配置:
module.exports = {entry: {},module: {},...devServer: {historyApiFallback: true,proxy: [{context: '/login',target: 'http://www.demo2.com:8080',// 代理跨域目标接口changeOrigin: true,secure: false,// 当代理某些https服务报错时用cookieDomainRewrite: 'www.demo1.com'// 可以为false,表示不修改}],noInfo: true}
}
WebSocket协议跨域
WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
前端代码:
<div>user input:<input type="text"></div>
<script src="./socket.io.js"></script>
<script> var socket = io('http://www.demo2.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后台:
// 启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.'); });
});
最后
最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。
有需要的小伙伴,可以点击下方卡片领取,无偿分享