一、浏览器的同源策略
1.什么是同源?
所谓“同源”指的是”三个相同“。相同的域名、端口和协议,这三个相同的话就视为同一个域,本域下的JS脚本只能读写本域下的数据资源,无法访问其它域的资源。
协议相同
域名相同
端口相同(如果没有写端口,默认是80端口)
2.什么是同源策略?
同源策略是浏览器为了保护用户的个人信息以及企业数据的安全而设置的一种策略,不同源的客户端脚本是不能在对方未允许的情况下访问或索取对方的数据信息;
3.同源策略的目的
同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。
设想这样一种情况:A 网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取 A 网站的 Cookie,会发生什么?
很显然,如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。
由此可见,“同源政策”是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。
4.非同源的限制
随着互联网的发展,“同源政策”越来越严格。目前,如果非同源,共有三种行为受到限制。
(1) Cookie、LocalStorage 和 IndexedDB 无法读取。
(2) DOM 无法获得。
(3) AJAX 请求无效(可以发送,但浏览器会拒绝接受响应)。
5.什么是跨域?
跨域就是解决同源策略带来的不便,突破同源策略的限制去获取不同源之间的数据信息或者进行不同源之间的信息传递。
二、跨域的几种实现方法
1. JSONP
1.1什么是JSONP
JSONP是JSON with padding(填充式JSON或参数式JSON)的简写,是应用JSON的一种新方法。JSONP看起来与JSON差不多,只不过是被包含在函数调用中的JSON,就像下面这样。
callback({ "name": "Nicholas" });
JSONP由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。而数据就是传入回调函数的JSON数据。
简单来说就是利用并提供一个回调函数来接收数据(函数名可约定),响应传到来时传递过来的数据为json数据的包装(故称之为jsonp,即json padding)。 传过来的数据类似: callback({"name":"hax","gender":"Male"})
。因为JSONP是有效的JavaScript代码,所以在请求完成后,即在JSONP响应加载到页面中以后,浏览器会立即执行callback函数,并传递解析后的json对象作为参数。
1.2JSONP的原理
jsonp其实就是利用<script>元素本身可跨域,可以将其src属性里指定的路径里的资源下载下来的设定,从而达到跨域的目的。
JSONP是通过动态创建<script>元素来使用的,使用时为src属性指定一个跨域URL。<script>元素与<img>元素类似,都有能力不受限制地从其他域加载资源。因为JSONP是有效的JavaScript代码,所以在请求完成后,即在JSONP响应加载到页面中以后,就会立即执行。
1.3JSONP的使用步奏
本域:
- 首先动态创建script标签;
var script = document.createElement('script');
- 创建回调函数callback(假定函数名为appendHtml),然后将该函数与callback字段结合成键值对的形式,例如:callback=appendHtml,接着将其与要访问的远端(不同源)的接口url(假设要访问的url为http://localhost:8080)结合成如下形式:
script.src = 'http://localhost:8080/getNews?callback=appendHtml';
服务器部分:
获取到回调函数appendHtml后,把需要发送的数据与函数appendHtml进行包装,使用字符串拼接的方式组成如下形式再发回给本域:
aaa({"name": "xiaoming", "age": "1000"});
请求完成后后:浏览器会调用回调函数appendHtml,把获得的数据以参数形式传递进去,进行数据处理;
PS:由上述步骤可见,jsonp的使用是需要远端支持的。
1.4JSONP的优缺点
优点:
- 简单易用,能够直接访问响应文本,支持在浏览器与服务器之间双向通信。
缺点:
因为src属性自己获取数据要在url后面加上数据参数,那么这个方式就只有get,所以JSONP也只能用get方式获取数据;
JSONP只能解决跨域获取资源问题,但是不能解决不同域页面之间的JS调用问题;
安全性问题:由于JSONP是从其他域中加载代码执行,如果其他域不安全,很可能会在响应中夹带一些恶意代码,而此时除了完全放弃JSONP调用之外,没有办法追究;
要半段JSONP请求失败并不容易,它不会像ajax那样如果失败的话会返回失败的http状态码;
2.CORS
2.1什么是CORS?
CORS是一个W3C标准,全称是“跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest
请求,从而克服了AJAX只能同源使用的限制。
CORS需要浏览器和服务器同时支持。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨域,就会自动添加一些附加的头信息。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨域通信。
2.2CORS的原理
如果浏览器发现这次是跨域的AJAX
请求,就会在请求头信息之中,增加一个Origin
字段。Origin
字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
如果Origin
指定的源,不在许可范围内,服务器会返回一个正常的HTTP
回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin
字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest
的onerror
回调函数捕获。
如果Origin指定的域名在许可范围内,服务器返回的响应,会多出响应头信息字段。
Access-Control-Allow-Origin: Origin的值,即本次请求来源哪个源(协议 + 域名 + 端口)。
Access-Control-Allow-Origin
该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
2.3CORS的实现方式
CORS通信的实现只能依赖跨域服务器的支持,而在本域下的的代码只是普通的AJAX请求。
通过在跨域服务器中对响应头进行设置,实现对指定的域允许进行数据通信,如下代码是对响应头进行的设置:
header("Access-Control-Allow-Origin", "http://a.jrg.com:8080")
这个代码实现了 http://a.jrg.com:8080
对其数据的访问;
2.4CORS跨域的实现步奏
本域:发出普通的AJAX请求
跨域服务器:在服务器端通过设置
header
属性来指定允许跨域的源地址。如:header("Access-Control-Allow-Origin","http://a.jrg.com:8080");
,表明允许http://a.jrg.com:8080
这个源地址获取数据。在AJAX请求发过来之后,如果发送AJAX请求的地址是http://a.jrg.com:8080
,则在返回的数据中添加响应头信息header('Access-Control-Allow-Origin', '允许跨域进行访问的域名')
,这里是header("Access-Control-Allow-Origin","http://a.jrg.com:8080"),如果在服务器端header("Access-Control-Allow-Origin","*");
这么设置,表明允许所有的域都可以对其进行跨域访问。本域分两种情况:
1、已经被允许跨域访问:在响应头处出现一个键值对,如:Access-Control-Allow-Origin: http://a.com:8080
。
2、未允许进行跨域访问:
①:可能是跨域服务器不支持CORS跨域访问,那么就不会有类似Access-Control-Allow-Origin: http://a.com:8080
的响应头信息。
②:跨域服务器不支持本域进行访问,也会有回应头信息,该信息标注那些域是可以进行访问的,比如:跨域服务器支持a.com
进行访问,而我用b.com
对其进行访问,回应头就会回复Access-Control-Allow-Origin: http://a.com:8080
字段,表明只有a.com是支持访问的;出现如下错误:
2.5示例代码
本域:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>news</title>
<style>
.container{
width: 900px;
margin: 0 auto;
}
</style>
</head>
<body>
<div class="container">
<ul class="news">
<li>第11日前瞻:中国冲击4金 博尔特再战200米羽球</li>
<li>男双力争会师决赛</li>
<li>女排将死磕巴西!</li>
</ul>
<button class="change">换一组</button>
</div>
<script>
$('.change').addEventListener('click',function(){
var xhr = new XMLHttpRequest();
//向http://b.jrg.com:8080地址请求数据,本域的地址为http://a.jrg.com:8080
xhr.open('get','http://b.jrg.com:8080/getNews',true);
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if (xhr.status === 200 || xhr.status === 304) {
appendHtml(JSON.parse(xhr.responseText))
}
}
}
})
function appendHtml(news){
var html = '';
for(var i = 0; i<news.length;i++){
html +='<li>' + news[i] + '</li>';
}
console.log(html);
$('.news').innerHTML = html;
}
function $(id){
return document.querySelector(id)
}
</script>
</body>
</html>
服务器部分:
app.get('/getNews',function(req,res){
var news = [
"1.第11日前瞻:中国冲击4金 博尔特再战200米羽球",
"2.正直播柴彪、宏伟出站",
"3.女排将死磕巴西",
"4.没有中国选手和巨星的110米栏 我们还看吗",
"5.中英上演奥运会金牌大战",
"6.博彩赔率挺中国夺回第二纽约时报",
"7.最”出柜奥运?“",
"8.下跪与洪荒之力"
]
var data = [];
for(var i = 0; i < 3; i++){
var index = parseInt(Math.random()*news.length);
data.push(news[index]);
news.splice(index,1);
}
//通过设置属性header,允许来自http://a.jrg.com:8080地址的AJAx请求
res.header("Access-Control-Allow-Origin","http://a.jrg.com:8080");
//允许来自任何域、的AJAx请求
//res.header("Access-Control-Allow-Origin","*")
res.send(data);
})
2.6CORS与JSON的对比
jsonp比CORS优秀的地方:
- jsonp兼容性较好,而CORS在IE中只兼容IE10以上浏览器,此外在IE7或以下的IE浏览器中,因为没有XMLHttpRequest对象,只支持ActiveX对象,所以注定无法使用CORS,而jsonp这时候就可以大放异彩;
CORS比jsonp优秀的地方:
CORS在前端部分只需要发送普通的AJAX请求即可,使用起来和不跨域时并无不同,更加的方便;
因为第一条,所以CORS支持其它的请求方式(比如post、put等);
如何选择:
- 在有选择的情况下,兼容老浏览器可以使用jsonp,主流浏览器可以选用CORS;
3.降域
3.1什么是降域
降域就是当两个一级域名相同但二级域名不同时(如:a.xgj.com和b.xgj.com中一级都是xgj.com,a和b是主机名),对两个域名都设置document.domain = 一级域名来达到跨域的目的;
3.2降域的限制性
使用降域来达成跨域的目的有非常大的限制性:
主域名要相同:a.com和b.com就不行,a.oxc.com和b.oxc.com就可以;
降域只适用于iframe窗口和获取cookie,但不能获取LocalStorage 和 IndexDB ;
3.3降域例子
实现功能:当在a.xgj.com的输入框中输入字符,b.xgj.com的输入框中也会出现相同字符
在a页面(a.xgj.com页面)使用<iframe>嵌入b页面(b.xgj.com页面)
a页面代码:
<body>
<div class="ct">
<h1>使用降域实现跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.xgj.com:8080/a.html">
</div>
<iframe src="http://b.xgj.com:8080/b.html" frameborder="0"></iframe>
<script>
document.querySelector('.main input').addEventListener('input',function(){
console.log(this.value);
//先用window.frames[0]获取iframe节点,因为iframe加载的是另一个html所以也有document,之后用document.querySelector('input')获取input节点
/*如果文档包含框架(frame 或 iframe 标签),浏览器会为 HTML 文档创建一个 window 对象,并为每个框架创建一个额外的 window 对象。
window.frames:返回窗口中所有命名的框架。
该集合是 Window 对象的数组,每个 Window 对象在窗口中含有一个框架或 <iframe>。属性 frames.length 存放数组 frames[] 中含有的元素个数。注意,frames[] 数组中引用的框架可能还包括框架,它们自己也具有 frames[] 数组。
*/
window.frames[0].document.querySelector('input').value = this.value;
})
document.domain = "jrg.com";
</script>
</body>
b页面代码:
<body>
<input type="text" id="input" placeholder="http://b.jrg.com">
<script>
document.querySelector('#input').addEventListener('input',function(){
window.parent.document.querySelector('input').value = this.value;
})
document.domain = 'jrg.com';
</script>
</body>
效果图:
4、postMessage
4.1什么是postMessage?
HTML5为了解决跨域问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。
这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。
举例来说,父窗口aaa.com向子窗口bbb.com发消息,调用postMessage方法就可以了。
var popup = window.open('http://bbb.com', 'title');
popup.postMessage('Hello World!', 'http://bbb.com');
postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即“协议 + 域名 + 端口”。也可以设为*,表示不限制域名,向所有窗口发送。
4.2postMessage使用例子
实现功能:当在a.xgj.com的输入框中输入字符,b.xgj.com的输入框中也会出现相同字符;
a页面:
<body>
<div class="ct">
<h1>使用postMessage实现跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.jrg.com:8080/a.html">
</div>
<iframe src="http://localhost:8080/b.html" frameborder="0"></iframe>
</div>
<script>
$('.main input').addEventListener('input',function(){
console.log(this.value);
//给iframe发消息
window.frames[0].postMessage(this.value,'*');
})
//接收别人的消息要去监听message事件
window.addEventListener('message',function(e){
$('.main input').value = e.data;
console.log(e.data);
});
function $(id){
return document.querySelector(id);
}
</script>
</body>
b页面:
<body>
<input type="text" id="input" placeholder="http://b.jrg.com:8080/b.html">
<script>
$('#input').addEventListener('input',function(){
window.parent.postMessage(this.value,'*');
})
window.addEventListener('message',function(e){
$('#input').value = e.data;
console.log(e.data);
})
function $(id){
return document.querySelector(id);
}
</script>
</body>