Open EmberYu opened 5 years ago
身为一个前端开发,跨域算是最常见的一种问题了,在开发中如果遇到请求在console报下面这个错误,那么基本上就肯定是遇到跨域问题了。
在了解跨域之前,我们首先应该要知道什么是同源策略,假设没有同源策略是浏览器安全的基石,如果不满足同源策略,那么有三种行为将受到限制
同源策略
Cookie、LocalStorage和IndexDB无法读取
DOM无法获得
AJAX请求不能发送
必须满足以下三个条件,两个页面才可以被称之为同源
同源
所以,不是同源之间的请求,我们称之为跨域请求
跨域请求
假设A网站是一家银行网页,那么用户在登录A网站进行消费之后,忘记关闭页面,此时打开了一个新的页面B页面,如果没有同源政策,那么B页面可以携带A的cookie登录信息去A网页的后端API发起转账请求,这是一种十分危险的操作。由此可见,同源政策可以更好的保护用户隐私,是有必要性的。
我们先看一下以下几个域名
https://www.qq.com
https://qzone.qq.com
https://mail.qq.com
以上三个域名,我们可以发现都是属于QQ旗下的,互相之间也不是同源(一级域名相同,但是二级域名不同)。那么假设我们不能共享彼此的Cookie,那么我们是否需要维护三套登录系统API,三套Cookie呢?这对开发来说,相当于无形中增加了不必要的重复工作。所以,在确保安全的情况下,有时候我们需要某些方法来实现跨域
Cookie
jsonp是最常见的跨域解决方案之一了,主要是它具有良好的兼容性。我们知道,<script>标签是可以加载不同域的资源的(不然我们也无法直接引入某些框架的CDN文件使用)。而且<script>标签加载完成后其内的代码会自动执行。那么我们如果想要实现某个跨域请求,可以用下面几个步骤
<script>
在本地声明跨域的方法
var jsonpFunc = function (params) { // dosomeThing... }
动态的创建<script>标签,请求至目标接口,返回对该方法的调用
var jsonpScript = document.createElement('script'); jsonpScript.type = 'text/javascript' // 这里一般与后端约定传入callback参数来告知后端函数名,由后端来返回对该函数名的调用 // 返回值为 jsonpFunc(param); jsonpScript.src = 'http://someApi.com?callback=jsonpFunc'; document.head.appendChild(script);
两个页面通过js强制设置document.domain为同一主域,就实现了同域
document.domain
这种方法只适用于一级域名相同,二级域名不同的页面
父窗口:www.domain.com/a.html
www.domain.com/a.html
<iframe id="frame" src="http://mail.domain.com/b.html"></iframe> <script> document.domain = 'domain.com' var user = 'admin'; </script>
子窗口:mail.domain.com/b.html
mail.domain.com/b.html
<script> document.domain = 'domain.com' alert(window.parent.user) // 'admin' </script>
利用hash值改变不刷新页面的特性,通过监听hashchange事件来实现跨域通信
hashchange
A想要与B通信,可以通过中间页C来实现,三个页面利用iframe的location.hash传值。
A页面: www.domain.com/a.html
<iframe id="iframe" src="http://mail.domain.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页面: mail.domain.com/b.html
<iframe id="iframe" src="http://www.domain.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页面: www.domain.com/c.html
www.domain.com/c.html
<script> // 监听b.html传来的hash值 window.onhashchange = function () { // 再通过操作同域a.html的js回调,将结果传回 window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', '')); }; </script>
原理就是A页面嵌套B页面,B页面中嵌套C页面,虽然hash是单向传递的,但是因为C跟A同源,所以A的变更传到B,B的变更传到C以后,C页面能通过parent.parent访问同源的A页面
hash
parent.parent
利用window.name在不同页面加载后依然存在,并且可以支持非常长的值(2MB)
var proxy = function(url, callback) { var state = 0; var iframe = document.createElement('iframe'); // 加载跨域页面 iframe.src = url; // onload事件会触发2次,第1次加载跨域页,并留存数据于window.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.domain.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://mail.domain.com/b.html', function(data){ alert(data); });
<script> window.name = 'This is domain2 data!'; </script>
proxy页面: www.domain.com/proxy.html
www.domain.com/proxy.html
利用同一页面跳转会保留window.name值,具体原理为
window.name
iframe
onload
url
proxy
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一
postMessage主要用来解决以下问题
页面和其打开的新窗口的数据传递
多窗口之间消息传递
页面与嵌套的iframe消息传递
上面三个场景的跨域数据传递
postMessage接收两个参数
具体可以看MDN的postMessage
只服务端设置Access-Control-Allow-Origin即可,前端无须设置。
带cookie请求
请求需要打开withCredentials
withCredentials
var xhr = new XMLHttpRequest(); xhr.withCredentials = true;
请求需要加上Origin请求头,值为当前域名
Origin
服务器端响应头需要加上Access-Control-Allow-Origin,值为上面的当前域名
Access-Control-Allow-Origin
服务器端响应头需要加上Access-Control-Allow-Credentials,值为true
Access-Control-Allow-Credentials
true
目前,所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)),CORS也已经成为主流的跨域解决方案。
WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。
websocket
浏览器同源政策及其规避方法 - 阮一峰老师
前端常见跨域解决方案
浏览器中的跨域
身为一个前端开发,跨域算是最常见的一种问题了,在开发中如果遇到请求在console报下面这个错误,那么基本上就肯定是遇到跨域问题了。
什么是跨域?
在了解跨域之前,我们首先应该要知道什么是
同源策略
,假设没有同源策略
是浏览器安全的基石,如果不满足同源策略
,那么有三种行为将受到限制Cookie、LocalStorage和IndexDB无法读取
DOM无法获得
AJAX请求不能发送
必须满足以下三个条件,两个页面才可以被称之为
同源
所以,不是同源之间的请求,我们称之为
跨域请求
跨域的意义?
假设A网站是一家银行网页,那么用户在登录A网站进行消费之后,忘记关闭页面,此时打开了一个新的页面B页面,如果没有同源政策,那么B页面可以携带A的cookie登录信息去A网页的后端API发起转账请求,这是一种十分危险的操作。由此可见,同源政策可以更好的保护用户隐私,是有必要性的。
为什么要解决跨域?
我们先看一下以下几个域名
https://www.qq.com
https://qzone.qq.com
https://mail.qq.com
以上三个域名,我们可以发现都是属于QQ旗下的,互相之间也不是同源(一级域名相同,但是二级域名不同)。那么假设我们不能共享彼此的
Cookie
,那么我们是否需要维护三套登录系统API,三套Cookie呢?这对开发来说,相当于无形中增加了不必要的重复工作。所以,在确保安全的情况下,有时候我们需要某些方法来实现跨域跨域的解决方案
Jsonp
jsonp是最常见的跨域解决方案之一了,主要是它具有良好的兼容性。我们知道,
<script>
标签是可以加载不同域的资源的(不然我们也无法直接引入某些框架的CDN文件使用)。而且<script>
标签加载完成后其内的代码会自动执行。那么我们如果想要实现某个跨域请求,可以用下面几个步骤在本地声明跨域的方法
动态的创建
<script>
标签,请求至目标接口,返回对该方法的调用document.domain + iframe
这种方法只适用于一级域名相同,二级域名不同的页面
父窗口:
www.domain.com/a.html
子窗口:
mail.domain.com/b.html
location.hash + iframe跨域
A想要与B通信,可以通过中间页C来实现,三个页面利用iframe的location.hash传值。
A页面:
www.domain.com/a.html
B页面:
mail.domain.com/b.html
C页面:
www.domain.com/c.html
原理就是A页面嵌套B页面,B页面中嵌套C页面,虽然
hash
是单向传递的,但是因为C跟A同源,所以A的变更传到B,B的变更传到C以后,C页面能通过parent.parent
访问同源的A页面window.name + iframe跨域
A页面:
www.domain.com/a.html
B页面:
mail.domain.com/b.html
proxy页面:
www.domain.com/proxy.html
利用同一页面跳转会保留
window.name
值,具体原理为iframe
加载B页面,其中B页面修改了iframe
的window.name
iframe
的onload
第一次加载完毕之后,将iframe
的url
转向同源的proxy
页面iframe
的链接变动,window.name
值会被保留,所以在A页面中能访问到被B修改的window.name
值postMessage跨域
postMessage主要用来解决以下问题
页面和其打开的新窗口的数据传递
多窗口之间消息传递
页面与嵌套的iframe消息传递
上面三个场景的跨域数据传递
postMessage接收两个参数
具体可以看MDN的postMessage
跨域资源共享(CORS)
只服务端设置Access-Control-Allow-Origin即可,前端无须设置。
带cookie请求
请求需要打开
withCredentials
请求需要加上
Origin
请求头,值为当前域名服务器端响应头需要加上
Access-Control-Allow-Origin
,值为上面的当前域名服务器端响应头需要加上
Access-Control-Allow-Credentials
,值为true
目前,所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)),CORS也已经成为主流的跨域解决方案。
WebSocket协议跨域
WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。
websocket
鸣谢
浏览器同源政策及其规避方法 - 阮一峰老师
前端常见跨域解决方案