本文介绍: 跨域问题其实就是浏览器同源策略造成的。下表给出了与 URL 的源进行对比的示例:同源策略protocol协议)、domain域名)、port端口)三者必须一致。**同源政策主要限制了三个方面:同源政策的目的主要是为了保证用户信息安全,它只是对 js 脚本的一种限制,并不是对浏览器限制,对于一般的 img、或者script 脚本请求都不会有跨域的限制,这是因为这些操作都不会通过响应结果来进行可能出现安全问题操作。下面是MDN对于CORS的定义:CORS需要浏览器服务器同时支持,整个CORS

1. 同源策略

跨域问题其实就是浏览器的同源策略造成的。

同源策略限制了从同一个加载文档或脚本如何与另一个源的资源进行交互。这是浏览器一个用于隔离潜在恶意文件的重要的安全机制。同源指的是:协议端口号域名必须一致。

下表给出了与 URL [http://store.company.com/dir/page.html](http://store.company.com/dir/page.html) 的源进行对比的示例:

URL 是否跨域 原因
store.company.com/dir/page.ht… 同源 完全相同
store.company.com/dir/inner/a… 同源 只有路径不同
store.company.com/secure.html 跨域 协议不同
store.company.com:81/dir/etc.htm… 跨域 端口不同 ( http:// 默认端口是80)
news.company.com/dir/other.h… 跨域 主机不同

同源策略protocol(协议)、domain域名)、port端口)三者必须一致。**同源政策主要限制了三个方面:

同源政策的目的主要是为了保证用户信息安全,它只是对 js 脚本的一种限制,并不是对浏览器的限制,对于一般的 img、或者script 脚本请求都不会有跨域的限制,这是因为这些操作都不会通过响应结果来进行可能出现安全问题的操作。

2. 如何解决跨越问题

(1)CORS

下面是MDN对于CORS的定义

跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器运行一个 origin (domain)上的Web应用被准许访问来自不同源服务器上的指定资源。当一个资源从与该资源本身所在的服务器不同的域、协议端口请求一个资源时,资源会发起一个跨域HTTP 请求

CORS需要浏览器和服务器同时支持,整个CORS过程都是浏览器完成的,无需用户参与。因此实现CORS的关键就是服务器,只要服务器实现了CORS请求,就可以跨源通信了。

浏览器将CORS分为简单请求简单请求

简单请求不会触发CORS预检请求。若该请求满足以下两个条件,就可以看作是简单请求:1)请求方法是以下三种方法之一:

  • HEAD
  • GET
  • POST

2)HTTP的头信息不超出以下几种字段

若不满足以上条件,就属于简单请求了。

**(1)简单请求过程:**对于简单请求,浏览器会直接发出CORS请求,它会在请求的头信息中增加一个Orign字段,该字段用来说明本次请求来自哪个源(协议+端口+域名),服务器会根据这个值来决定是否同意这次请求。如果Orign指定域名在许可范围之内,服务器返回响应就会多出以下信息头:

Access-Control-Allow-Origin: http://api.bob.com// 和Orign一直
Access-Control-Allow-Credentials: true // 表示是否允许发送Cookie
Access-Control-Expose-Headers: FooBar // 指定返回其他字段的值
Content-Type: text/html; charset=utf-8 // 表示文档类型 

如果Orign指定域名不在许可范围之内,服务器会返回一个正常的HTTP回应,浏览器发现没有上面的Access-Control-Allow-Origin头部信息,就知道出错了。这个错误无法通过状态识别,因为返回状态码可能是200。

在简单请求中,在服务器内,至少需要设置字段Access-Control-Allow-Origin

(2)非简单请求过程非简单请求是对服务器有特殊要求的请求,比如请求方法为DELETE或者PUT等。非简单请求的CORS请求会在正式通信之前进行一次HTTP查询请求,称为预检请求

浏览器会询问服务器,当前所在的网页是否在服务器允许访问的范围内,以及可以使用哪些HTTP请求方式和头信息字段,只有得到肯定的回复,才会进行正式的HTTP请求,否则就会报错

预检请求使用请求方法是OPTIONS表示这个请求是来询问的。他的头信息中的关键字段是Orign,表示请求来自哪个源。除此之外,头信息中还包括两个字段

服务器在收到浏览器的预检请求之后,会根据头信息的三个字段来进行判断,如果返回的头信息在中有Access-Control-Allow-Origin这个字段就是允许跨域请求,如果没有,就是不同意这个预检请求,就会报错

服务器回应的CORS的字段如下

Access-Control-Allow-Origin: http://api.bob.com// 允许跨域的源地址
Access-Control-Allow-Methods: GET, POST, PUT // 服务器支持的所有跨域请求的方法
Access-Control-Allow-Headers: X-Custom-Header// 服务器支持的所有头信息字段
Access-Control-Allow-Credentials: true // 表示是否允许发送Cookie
Access-Control-Max-Age: 1728000// 用来指定本次预检请求的有效期,单位为秒 

只要服务器通过了预检请求,在以后每次的CORS请求都会自带一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

在非简单请求中,至少需要设置以下字段:

'Access-Control-Allow-Origin''Access-Control-Allow-Methods'
'Access-Control-Allow-Headers' 

减少OPTIONS请求次数

OPTIONS请求次数过多就会损耗页面加载性能,降低用户体验度。

所以尽量要减少OPTIONS请求次数,可以后端在请求的返回头部添加:Access-Control-Max-Age:number。它表示预检请求的返回结果可以被缓存多久,单位是秒。

该字段只对完全一样的URL的缓存设置生效,所以设置了缓存时间,在这个时间范围内,再次发送请求就不需要进行预检请求了。

CORS中Cookie相关问题

在CORS请求中,如果想要传递Cookie,就要满足以下三个条件

默认情况下在跨域请求,浏览器是不带 cookie 的。但是我们可以通过设置 withCredentials 来进行传递 cookie.

// 原生 xml 的设置方式
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
// axios 设置方式
axios.defaults.withCredentials = true; 
  • Access-Control-Allow-Credentials 设置为 true
  • Access-Control-Allow-Origin 设置为为非 *
(2)JSONP

jsonp原理就是利用<script>标签没有跨域限制,通过<script>标签src属性,发送带有callback参数的GET请求,服务端接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回数据

1)原生JS实现:

<script>var script = document.createElement('script');script.type = 'text/javascript';// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义回调函数script.src = 'http://www.domain2.com:8080/login?user=admin&amp;callback=handleCallback';document.head.appendChild(script);// 回调执行函数function handleCallback(res) {alert(JSON.stringify(res));}
 </script> 

服务端返回如下(返回时即执行全局函数):

handleCallback({"success": true, "user": "admin"}) 

2)Vue axios实现:

this.$http = axios;
this.$http.jsonp('http://www.domain2.com:8080/login', {params: {},jsonp: 'handleCallback'
}).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 = querystring.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...'); 

JSONP的缺点:

(3)postMessage 跨域

postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:

用法postMessage(data,origin)方法接受两个参数

1)a.html:(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:(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;// 处理后再发回domain1window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');}}, false);
</script> 

(4)nginx代理跨域

nginx代理跨域,实质和CORS跨域原理一样,通过配置文件设置请求响应头Access-Control-Allow-Origin…等字段。1)nginx配置解决iconfont跨域浏览器跨域访问jscss、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置

location / {add_header Access-Control-Allow-Origin *;
} 

2)nginx反向代理接口跨域跨域问题:同源策略仅是针对浏览器的安全策略。服务器端调用HTTP接口只是使用HTTP协议,不需要同源策略,也就不存在跨域问题。实现思路:通过Nginx配置一个代理服务器域名domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前cookie写入,实现跨域访问。

nginx具体配置

#proxy服务器
server {listen 81;server_namewww.domain1.com;location / {proxy_pass http://www.domain2.com:8080;#反向代理proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie域名indexindex.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;}
} 

(5)nodejs 中间件代理跨域

node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie域名,实现当前域的cookie写入,方便接口登录认证1)非vue框架的跨域使用node + express + http-proxymiddleware搭建一个proxy服务器。

var xhr = new XMLHttpRequest();
// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;
// 访问http-proxy-middleware代理服务器
xhr.open('get', 'http://www.domain1.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.domain2.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.domain1.com'// 可以为false表示不修改
}));
app.listen(3000);
console.log('Proxy server is listen at port 3000...'); 

2)vue框架的跨域node + vue + webpack + webpackdevserver搭建项目,跨域请求接口,直接修改webpack.config.js配置开发环境下,vue渲染服务和接口代理服务都是webpackdevserver同一个,所以页面与代理接口之间不再跨域。

webpack.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}
} 

(6)document.domain + iframe跨域

方案仅限主域相同,子域不同的跨域应用场景。实现原理两个页面都通过js强制设置document.domain为基础主域,就实现了同域。1)父窗口:(domain.com/a.html)

<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script> document.domain = 'domain.com';var user = 'admin'; </script> 

1)子窗口:(child.domain.com/a.html)

<script>document.domain = 'domain.com';// 获取父窗口中变量console.log('get js data from parent ---> ' + window.parent.user);
</script> 

(7)location.hash + iframe跨域

实现原理:a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframelocation.hash传值,相同域之间直接js访问来通信

具体实现:A域:a.html -> B域:b.html -> A域:c.html,a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象

1)a.html:(domain1.com/a.html)

<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script> var iframe = document.getElementById('iframe');// 向b.html传hashsetTimeout(function() {iframe.src = iframe.src + '#user=admin';}, 1000);// 开放给同域c.html的回调方法function onCallback(res) {alert('data from c.html ---> ' + res);} </script> 

2)b.html:(.domain2.com/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.htmlwindow.onhashchange = function () {iframe.src = iframe.src + location.hash;};
</script> 

3)c.html:(www.domain1.com/c.html)

<script>// 监听b.html传来的hash值window.onhashchange = function () {// 再通过操作同域a.html的js回调,将结果传回window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));};
</script> 

(8)window.name + iframe跨域

window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

1)a.html:(domain1.com/a.html)

var proxy = function(url, callback) {var state = 0;var iframe = document.createElement('iframe');// 加载跨域页面iframe.src = url;// onload事件触发2次,第1次加载跨域页,并留存数据于window.nameiframe.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);
}); 

2)proxy.html:(domain1.com/proxy.html)

中间代理页,与a.html同域,内容为空即可。3)b.html:(domain2.com/b.html)

<script>window.name = 'This is domain2 data!';
</script> 

通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

(9)WebSocket协议跨域

WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。

原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容

1)前端代码

<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> 

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.'); });
}); 

最后

最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

原文地址:https://blog.csdn.net/weixin_53312997/article/details/127774619

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_22256.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注