前段时间学习了AJAX,已经可以从后台拿到JSON串。可是出现了问题,目前我发送的请求都是在同域下的请求,如果我想拿到其它域的数据,便会受到浏览器的同源策略限制,于是便学习的跨域的相关知识,这篇文章便是用于对这些知识进行复习。
一、关于浏览器的同源策略和跨域
什么是同源?
同源就是相同的域名,端口和协议,这三个相同的话就视为同一个域,本域下的JS脚本只能读写本域下的数据资源,无法访问其它域的资源,例如:
同协议:都是http或者都是https;
同域名:都是oxc.com;
同端口:都是8080或者3000;什么是同源策略?
同源策略是浏览器为了用户的个人信息以及企业数据的安全而设置的一种策略,不同源的客户端脚本是不能在对方未允许的情况下访问或索取对方的资源;
同源策略的重要性
前面所说,同源策略是浏览器为了安全起见而设定,那么如果没有同源策略会发生什么事呢?
情景一:泄露信息,我登陆了淘宝,然后又登陆了一个恶意网站,恶意网站发送AJAX请求获取淘宝的信息接口,而这个时候我已经登陆过淘宝,所以浏览器附带了我个人的淘宝cookie,一起发送给恶意网站;
情景二:盗取数据,某网站花重金购买一部电影的播放权,我找到该网站接口后,直接把这部电影拿到手,然后搞到自己的网站上去播放;
由此可见,为了安全起见的同源策略是必不可少了,但同时也给ajax请求带来了很大的麻烦,比如大型网站不可能把整个网站的所有资源都放在同一个服务器上,这时候跨域就成了必不可少的技能;什么是跨域?
跨域就是解决同源策略带来的不便,通过JS去获取不同源之间的数据资源或者进行信息的传递。
跨域的几种实现方法
一、 jsonp
-
什么是jsonp?
JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。
-
jsonp的原理和使用步骤
jsonp其实就是利用script标签本身可跨域,并且可以将其src属性里的资源下载下来的设定从而达成目的。
具体使用步骤如下:
本域部分:
1.首先动态创建script标签;
2.创建回调函数callback(假定函数名为aaa),然后将该函数与callback字段结合成键值对的形式,例如:callback=aaa
,接着将其与远端(不同源)的接口url结合成如下形式:
http.....................php?callback=aaa
3.将创建的script标签的src引向结合后的接口http.....................php?callback=aaa
即可;
跨域服务器部分:
1.获取到回调函数aaa后,把需要发送的数据与函数aaa进行包装,使用字符串拼接的方式组成如下形式再发回给本域:
aaa({"name": "xiaoming", "age": "1000"})
;
这时候数据就已经到手了
最后:
浏览器会调用函数aaa,把获得的数据以参数形式传递进去,进行数据处理;
PS:由上述步骤可见,jsonp的使用是需要远端支持的。
-
jsonp的缺陷
1.因为src属性自己获取数据要在url后面加上数据参数,那么这个方式就只有get,所以jsonp也只能用get方式获取数据;
2.jsonp只能解决跨域获取资源问题,但是不能解决不同域页面之间的JS调用问题;
3.安全性问题:如果提供jsonp的远端存在注入漏洞,它返回的数据就有可能是被人操控的。那么调用过这个远端接口的所有网页就都有可能被操控;
4.jsonp调用失败不会返回失败的http状态码,有可能会是200OK;
-
小实例
二、CORS
-
什么是CROS?
CROS全称Cross-Origin Resource Sharing(跨域资源共享),它是一个W3C标准,支持使用AJAX向跨域服务器发出AJAX请求;
-
CORS的兼容性
截图自caniuse网
-
CORS的原理
个人类比:同源策略好比一个黑名单,这个黑名单非常严格,把所有的不同源客户端脚本都进行了限制访问,而CORS则是一个白名单,可以将允许访问的客户端脚本添加进这个白名单中,使其能进行访问;
-
实现方式
CORS通信的实现只能依赖跨域服务器的支持,而在本域下的的代码只是普通的AJAX请求;
通过在跨域服务器中对回应头
进行设置,实现对指定的域进行数据交互,如下代码是对回应头
进行的设置
header("Access-Control-Allow-Origin", "a.oxc.com")
这个代码实现了a.oxc.com
这个域名对其数据的访问;
-
步骤
1.本域:发出普通的AJAX请求
2.跨域服务器:添加回应头信息:header('Access-Control-Allow-Origin', '允许跨域进行访问的域名')
PS:这个回应头信息中Access-Control-Allow-Origin
是允许跨域访问,而后面的域名是允许进行跨域访问的域名,如果第二个参数是一个星号*
,就是无限制,所有的域都可以对其进行跨域访问;
3.本域分两种情况:
已经被允许跨域访问:
在回应头处出现一个键值对,如:Access-Control-Allow-Origin: http://a.com:8080
;
未允许进行跨域访问:
①:可能是跨域服务器不支持CORS跨域访问,那么就不会有类似:Access-Control-Allow-Origin: http://a.com:8080
的回应头信息;
②:跨域服务器不支持本域进行访问,也会有回应头信息,该信息标注那些域是可以进行访问的,比如:跨域服务器支持a.com进行访问,而我用b.com对其进行访问,回应头就会回复:Access-Control-Allow-Origin: http://a.com:8080
字段,表明只有a.com是支持访问的;
出现如下错误:
-
实际使用小例子
需求:用a.oxc.com
获取接口为b.oxc.com
的服务器上发回的信息
1.修改hosts文件:
2.写一个按钮,点击获取数据,接口号为:'http://b.oxc.com:8080/hello'
<button id="btn">点击</button>
<script>
var btn = document.querySelector('#btn');
btn.addEventListener('click', function(){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status === 200){
console.log(xhr.responseText)
}
}
}
xhr.open('get', 'http://b.oxc.com:8080/hello', true)
xhr.send();
})
3.跨域服务器设置回应头,只允许a.oxc.com
对其进行访问:
header('Access-Control-Allow-Origin', 'http://a.oxc.com:8080')
4.效果图:
-
其它的回应头设置
此外,我们还可以对回应头的一些信息进行设置:
1.Access-Control-Allow-Credentials
:
该信息指定是否发送cookie,可以设置true或者false,true是允许,false为不允许;
2.Access-Control-Expose-Headers
:
因为XMLHttpRequest对象中的getResponseHeader()方法只能接受6个基本的回应头信息:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma
,如果需要拿到更多的回应头信息就要对Access-Control-Expose-Headers
进行设置,例如:
Access-Control-Expose-Headers: aaa
就能拿到回应头信息中的aaa字段;
-
CORS与jsonp的对比
- jsonp比CORS优秀的地方
1.jsonp兼容性较好,而CORS在IE中只兼容IE10以上浏览器,此外在IE7或以下的IE浏览器中,因为没有XMLHttpRequest对象,只支持ActiveX对象,所以注定无法使用CORS,而jsonp这时候就可以大放异彩; - CORS比jsonp优秀的地方
1.CORS在前端部分只需要发送普通的AJAX请求即可,使用起来和不跨域时并无不同,更加的方便;
2.因为第一条,所以CORS支持其它的请求方式(比如post、put等); - 选型
在有选择的情况下,兼容老浏览器可以使用jsonp,主流浏览器可以选用CORS;
- jsonp比CORS优秀的地方
三、降域
-
什么是降域?
降域就是当两个一级域名相同但二级域名不同时(如:a.oxc.com和b.oxc.com中主域名都是oxc.com),对两个域名都设置document.domain = 主域名来达到跨域的目的;
-
降域的限制性
使用降域来达成跨域的目的有非常大的限制性:
1.主域名要相同:a.com和b.com就不行,a.oxc.com和b.oxc.com就可以;
2.降域只适用于iframe窗口和获取cookie,但不能获取LocalStorage 和 IndexDB ;
-
降域小例子
需求:当在a.oxc.com的输入框中输入字符,b.oxc.com的输入框中也会出现相同字符
1.写下a页面(a.oxc.com)和b(b.oxc.com)页面,其中b页面通过a页面的iframe标签嵌入了a页面
a页面:
<body>
<div>
<input type="text" id="ipt" placeholder="a.oxc">
</div>
<iframe src="http://b.oxc.com:8080/b.html" frameborder="0" scrolling="no"></iframe>
<script>
// 获取输入框
var ipt = document.querySelector('#ipt');
ipt.addEventListener('input', function(e) {
// 获取b的输入框输入的字段,让a的输入框字段相等
window.frames[0].ipt.value = this.value
console.log(this.value);
})
</script>
</body>
b页面:
<body>
<div>
<input type="text" id="ipt" placeholder="b.oxc">
</div>
<script>
var ipt = document.querySelector('#ipt');
ipt.addEventListener('input', function(e) {
// 让a的输入框的字段与b的输入框的字段相等
window.parent.frames.ipt.value = this.value;
})
</script>
</body>
2.为a和b页面分别添加document.domain = oxc.com
3.效果图:
4.a页面的cookie,b页面也可以进行读取
四、postMessage
-
什么是postMessage?
1.postMessage是window对象下的一个方法,他使得不同主域名之间也可以进行跨域通信:例如:使用a.com向b.com进行通信;
2.postMessage方法接收两个参数,第一个参数是需要发送的消息,第二个参数接收发送的域名,如:
window.postMessage('abc', 'a.com')
-
postMessage使用小例子一
需求:当在a.com的输入框中输入字符,b.com的输入框中也会出现相同字符;
代码如下:
a.com代码:
<div>
<input type="text" id="ipt" placeholder="a.oxc">
</div>
<iframe src="http://b.com:8080/b.html" frameborder="0" scrolling="no"></iframe>
<script>
var ipt = document.querySelector('#ipt');
ipt.addEventListener('input', function(e) {
window.frames[0].postMessage(this.value, 'http://b.com:8080')
console.log(this.value);
})
// 监听message事件
window.addEventListener('message', function(e){
ipt.value = e.data;
})
b.com代码:
<div>
<input type="text" id="ipt" placeholder="b.oxc">
</div>
<script>
var ipt = document.querySelector('#ipt');
ipt.addEventListener('input', function(e) {
window.parent.frames.postMessage(this.value, 'http://a.com:8080')
})
window.addEventListener('message', function(e){
ipt.value = e.data;
})
</script>
效果图:
- postMessage使用小例子二
1.a.com监听消息事件,并把接收到的消息放进输入框中;
2.在a.com中用window.open打开b.com,然后在b.com用window.opener.postMessage向a.com的输入框发送消息
步骤图: