跨域即跨域名的访问。
为何禁止跨域
由浏览器的同源策略导致的,为了防范跨站脚本的攻击,禁止客户端脚本对不同域下的文档或脚本进行跨站资源交互。
如果缺少同源策略,浏览器很容易受到XSS、CSFR等攻击。
同源
协议(http://)、域名(www.baidu.com)、端口号(:80),三者一致。
同源带来的问题
1、一级域名相同,二级域名不同的同意所有者的网页被限制;
2、无法跨域发送AJAX请求;
3、无法操作DOM。
为什么AJAX不可以跨域发送请求,而Form表单可以?
Form表单提交之后会刷新页面,所以即使跨域了也无法获取到数据,所以浏览器认为这个是安全的,而且AJAX最大的有点就是在不重新加载整个页面的情况下,更新部分页面内容,如果让他跨域,则可以读取到目标URL的私密信息,这将会变得非常危险,所以浏览器不允许AJAX发送跨域请求。
跨域报错
No 'Access-Control-Allow-Origin' header...
解决方案
1、jsonp
2、cors
3、nginx反向代理
4、node正向代理/nodejs中间件代理跨域
5、postMessage
6、websocket
7、vue中的proxyTable
8、iframe
+document.domain
+location.hash
+window.name
jsonp
Json With Padding,最早的解决方案。
动态创建script标签,利用src
不受同源策略约束的性质来实现跨域获取数据。
注意:
(1)、需要服务器的支持;
(2)、只能发起get请求;
(3)、携带数据较小;
(4)、要确定jsonp请求是否失败并不容易,一般用计时器检测指定时间内是否接收到了响应。
cors
Cross-origin resource sharing,是一个W3C标准,跨域资源共享。
它允许浏览器向跨源服务器,发出XMLHttpRequest请求。
需要浏览器(IE10以下不支持)和服务器同时支持。
浏览器端:会自动完成。
服务器端:一般通过过滤器过滤浏览器携带的一些头信息(用额外的HTTP头)判断是否运行其跨域,然后在响应头中加入一些信息。来告诉浏览器让该Web应用被准许访问来自不同源服务器上的指定的资源。(服务器设置Access-Control-Origin HTTP响应头,浏览器将允许跨域请求)
nginx反向代理
利用nginx反向代理把跨域为不跨域,支持各种请求方式。
缺点:需要再nginx进行额外配置,语义不清晰。
补充:
(1)、代理:不直接去做,找第三方来。
(2)、正向代理与反向代理: 正向代理代理客户端,反向代理代理服务器。
node正向代理/nodejs中间件代理跨域
服务端请求不会跨域,让接口和当前站点同源。
由nodejs服务,做路由转发API。
缺点:上线的时候,必须启动nodejs服务。
postMessage
postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信。
postMessage(data,origin)
data:顾名思义,是传递来的message。
origin:发送消息窗口的源(协议+主机+端口号)。
WebSocket协议跨域
一种网络通信协议。
HTML5开始提供的一种在单个TCP连接上进行全双工通讯的协议。
客户端和服务器之间存在持久的连接,而且双方都可以随时开始发送数据。
起因:
HTTP协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。
这种通信模型有一个弊端,HTTP协议无法实现服务器主动向客户端发起消息。
这种请求如果服务器有连续的状态变化,客户端会比较麻烦,大多数采用复杂的异步请求实现长轮询,轮询效率低,浪费资源。
WebSocket就是这样发明的,只要连接一次,就能一直保持连接状态。
vue中的proxyTable
vue中配置好的,设置一下就可以了。
//在config中的index.js配置:
dev{
proxyTable: {
'/api': {
target: 'www.name.com', // 要代理的域名
changeOrigin: true,//允许跨域
pathRewrite: {
'^/api': '' // 要访问的路径,名字随意
}
}
}
}
使用:
/api/getMenu
相当于http://www.name.com/getMenu
iframe
(1)、document.domain + Iframe
仅仅适用于:主域相同、子域不同时。
运行方式:
a.name.com
访问b.name.com
使用document.domain=name.com
(2)、window.location.hash + Iframe
通过url带hash,通过一个非跨域的中间页面来传递数据。
hash属性是一个可读可写的字符串,该字符串是URL的锚部分(#开始的部分)
运行方式:
A.html访问C.html的数据(两者跨域):
(B.html中间页面,与A同域)
A操作C,A通过location.hash
传递参数给C,C通过定时器检测hash变化,传给中间页面B,B监听C传来的hash值,再通过操作同域A的js回调,将结果传回。
//A.html
iframe.src="http://www.name.com/C.html"
iframe.onload = function() {
// 传递数据:age = 18;
iframe.src = "http://www.name.com/C.html#age=18";
}
function response(res) {
alert(res);
}
//C.html
window.onhashchange = function () {
iframe.src = "http://www.name.com/C.html" + location.hash;
}
//B.html
window.onhashchange = function () {
// => 再通过操作同域A的js回调,将结果传回
window.parent.parent.response(location.hash);
};
1)、动态改变location.hash,iframe不会重载;
2)、无论跨域与否,iframe内可以获取自己的location.hash;
3)、只要域名相同就能通信,即使ABC三层嵌套。
location.hash缺点:
1)、传递数据量有限;
2)、不太安全。
(3)、window.name + Iframe
window.name利用在一个浏览器窗口内,载入所有的域名都是共享一个window.name,当该 window 的 location 变化,然后重新加载,它的 name 属性可以依然保持不变。
运行方式:
A.html访问C.html的数据(两者跨域):
(B为空的中间页面,与A同域)
加载两次,第一次加载跨域页面,并保留数据于window.name,切换到B.htmliframe.src = "http://www.name.com/B.html"
第二次(B.html)加载成功后,读取同域名下的window.name中数据
// A.html:
iframe.src="http://www.name.com/C.html"
let first = true;
iframe.onload = () => {
if (first) {
iframe.src = "http://www.name.com/B.html";
first = false;
} else {
console.log(iframe.contentWindow.name);
}
}
//B.html
<div></div>
//C.html
window.name="xxx"