前言:跨域问题在工作中也是经常遇见的,今天想起来就自己写个文章记录一下,算是对自己零散的知识点做一个整理归纳吧。
一.浏览器同源策略
提到跨域就不得不提到浏览器的同源策略,同源策略时浏览器最核心也是最基本的安全功能,是指我们在发送网络请求时请求必须要遵守同源策略,即域名、协议、端口号都要相同。为了保证用户信息安全,防止恶意的网站窃取数据。示例
http://www.example.com/dir2/other.html:同源
http://example.com/dir/other.html:不同源(域名不同)
http://v2.www.example.com/dir/other.html:不同源(域名不同)
http://www.example.com:81/dir/other.html:不同源(端口不同)
这只是对浏览器同源策略的简单的概括,详细的请看浏览器同源政策及规避方法
二.解决方法
一.jsonp解决跨域
原理:动态添加一个 script 标签。而script 标签的src属性是没有跨域限制的。1.原生实现
实现代码:<script>
var script = document.creatElement('script');
script.type = 'text/javascript';
script.src = 'http://xxx.com?callBack=callBack';
document.head.appendChild(script);
function callBack(res){
alert(res)
}
</script>
2.jquery ajax:jq实现
$.ajax({
url:'http://xxx.com',
type:'get',
dataType:'jsonp',
jsonpCallback:'callBack',
data:{}
})
JSONP 跨域方式的优缺点分析
JSONP有点分析:它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完成后可以通过callback的方式回传结果。
JSONP缺点分析:只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。
二.document.domain + iframe跨域(仅限主域相同,子域不同的跨域场景应用)
两个页面都通过js强制设置documnet.domain为基础域,就实现了同域。(1)父窗口:(http://www.domain.com/a.html)
<iframe id='iframe' src='http://child.domain.com/b.html'></iframe>
<scripr>
document.domain = 'domain.com';
var res = 'data'
</scripr>
(2)子窗口:(http://child.domain.com/b.html)
<script>
document.domain = 'domain.com';
//获取父窗口中变量
console.log('res'+window.parent.res)
</script>
三.location.hash + iframe
因为父窗口可以对iframe进行URL读写,iframe也可以读写父窗口的URL,URL有一部分被称为hash,即#号及其后面的字符,一般用于浏览器的锚点定位,Server端并不关系这部分,HTTP请求过程中不会携带hash,所以这部分修改不会产生HTTP请求,但是会产生浏览器历史记录。此方法的原理就是改变URL的hash部分来进行双向通信。每个window通过改变其他window的location来发送消息(由于两个页面不在同一个域下IE,Chrome 不允许修改parent.location.hash的值,所以要借助于父窗口域名下的一个代理iframe),并通过监听自己的URL变化来接受消息。这个方式的通信会造成一些不必要的浏览器历史记录,而且有些浏览器不支持onhashChange事件,需要轮询来获取URL的改变,最后这样做也存在缺点,诸如数据直接暴露在url中了,数据容量和类型都有限,等等。 列:假如父页面是baidu.com/a.html,iframe嵌入的页面为google.com/b.html(此处省略了域名等url属性),要实现此两个页面的通信可以通过以下方法。
一、a.html传送数据到b.html
1.a.html下修改iframe的src为goodle.com/b.html#paco
2.b.html监听到url发生变化,触发相应操作
二、b.html传送数据到a.html,由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值 所以要借助于父窗口域名下的一个代理iframe
1.b.html下创建一个隐藏的iframe,此iframe的src是baidu.com域下的,并挂上要传送的hash数据,如src='www.baidu.com/proxy.html#...'\
2.proxy.html监听到url发生变化,修改a.html的url(因为a.html和proxy.html同域,所以proxy.html可修改a.html的url hash)
3.a.html监听到url发生变化,触发相应变化
b.html页面关键代码如下:
try{
parent.location.hash = 'data';
}catch(e){
var ifrpoxy = document.createElement('iframe');
ifrpoxy.style.display = 'none';
ifrpoxy.src = 'http://www.baidu.com/proxy.html#date'
};
//proxy.html页面的关键代码如下:
//因为parent.parent(即baidu.com/a.html)和baidu.com/proxy.html属于同一个域,所以可以改变其location.hash的值。
parent.parent.location.hash = self.location.hash.sybstring(1)
四、通过HTML5的postMessage方法实现跨域
postMessage是HTML5中的API,且是为数不多可以跨域操作的windows属性之一,可以用于解决以下问题- (1)页面和其它打开的新窗口的数据传递。
- (2)多窗口之间的消息传递。
- (3)页面与嵌套的iframe消息传递。
- (4)上面三个场景的跨域数据传递。
data:html5规范支持任意基本类型或可复制对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
origin:协议+主机+端口号,也可以设置为*,表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为'/'
//1) a.html:(http://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(e.data)
},false)
</script>
//2)b.html:(http://www.domain2.com/b.html)
<script>
//接受domain1的数据
window.addEventListener('message',function(e){
alert(e.data);
var data = JSON.parse(e.data);
if(data){
data.age = 18;
//处理返回domain1.
window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
}
})
</script>
六、cors 解决跨域问题
CORS是一个W3C标准,全称是"跨域资源共享"。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10.整个CORS通信过程都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头部信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信浏览器将CORS请求分成两类,简单请求和非简单请求.
只要同时满足以下两大条件,就属于简单请求。
(1)请求方法是一下三种方法之一:
.HEAD
.Get
.post
(2)HTTP的头信息不超出以下几种字段:
.Accept
.Accept-Language
.Content-Language
.Last-Event-Id
.Content-Type:只限于三个值application/x-www-from-urlencodel、miltipart/form-data、text/plain
凡是不同时满足上面两个条件,就属于非简单请求。
浏览器对这两种请求的处理,是不一样的。
AJAX的GET和POST方法的解决方案,这属于简单请求
对于简单请求
服务端:设置以下参数
Access-Control-Allow-Origin:*
//*代表允许所有的域名访问,写www.abc.com的话就是只允许来自该域名的跨域请求
Access-Control-Allow-Credentials:true
//CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,
指定Access-Control-Allow-Crendentuals字段。
客户端:
//需要在AJAX请求中打开withCredentails属性。
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
$.ajax({
type:'POST',
url:str,
xhrFields:{withCredentails:true},
contentType:'application/x-www-from-urlencoded',
data:data,
dataType:'json',
success:function(result){},
error:function(message){}
})
```检查发送出去的http请求,可以发现发出去的报文带上了Cookie和Origin的项,
服务器收到报文,检查Origin是否在它的Access-Control-Allow-Origin里面,是的话才提供服务。