同源策略
浏览器处于安全方面的考虑,只允许与本域下的接口交互。不同源的客户端脚本在没有明确授权的情况下,不能读写对方的资源。
解释同源之前先对url
进行解析

同源指的是:
- 协议相同
- 域名相同
- 端口相同
只要不满足上述3个条件的任意一个,即为不同源,也就是我们常说的跨域
同源策略的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据
不同源的几种情况:
- 协议不同
http://www.a.com/a.js
https://www.a.com/a.js
- 域名不同
http://www.a.com/a.js
http://www.b.com/a.js
- 端口不同
http://www.a.com:80/a.js
http://www.a.com:8080/a.js
注:对于当前页面来说页面存放的js
文件的域不重要,重要的是加载该js
页面所在什么域
如果不同源,共有3种行为受到限制
- 无法读取非同源网页的
Cookie、LocalStorage、IndexedDB
- 无法接触非同源网页的
DOM
- 无法向非同源地址发送
Ajax
请求(可以发送,但浏览器会拒绝接受响应)
跨域的几种实现方式
当网站A
需要获取网站B
的数据就涉及到跨域了,本文讲述的跨域方式有以下几种:
1. JSONP(JSON with Padding)
HTML
中的script
标签可以加载其他域下的js
,利用这点我们可以在网页上通过添加一个script
元素,向服务器请求JSON
数据,这种做法不受同源策略的限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来
<body>
<div class="container">
<ul class="news"></ul>
<button class="show">show news</button>
</div>
<script>
var ul = document.querySelector('.news');
var btn = document.querySelector('.show');
btn.addEventListener('click', function(){
var script = document.createElement('script');
script.setAttribute('src', 'http://127.0.0.1:8080/getNews?callback=appendHtml');
document.head.appendChild(script);
document.head.removeChild(script);
})
function appendHtml(news){
var html = '';
for(var i = 0; i < news.length; i++){
html += '<li>' + news[i] + '</li>';
}
ul.innerHTML = html;
}
</script>
</body>
var http = require('http');
var fs = require('fs');
var path = require('path');
var url = require('url');
http.createServer(function(req, res){
var pathObj = url.parse(req.url, true);
switch(pathObj.pathname){
case '/getNews':
var news = [
'苹果',
'香蕉',
'猕猴桃'
];
res.setHeader('Content-type', 'text/json; charset=utf-8');
if(pathObj.query.callback){
res.end(pathObj.query.callback + '(' + JSON.stringify(news) + ')');
}else {
res.end(JSON.stringify(news));
}
break;
default:
fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
if(e){
res.writeHead(404, 'not found');
res.end('<h1>404 not found</h1>');
}else {
res.end(data);
}
})
}
}).listen(8080);
使用node.js
搭建服务器,响应数据如下所示:

点击按钮时,页面呈现效果如下所示:

JSONP
的最大特点是老式浏览器全部支持,兼容性好,服务器的改造非常小;但缺点是只支持get
请求,且错误处理机制不如XMLHttpRequest
好
2. CORS(Cross-Origin Resource Sharing)
CORS
全称是跨域资源共享,是一种ajax
跨域请求资源的方式,支持现代浏览器,IE
支持10以上。实现方式很简单,当你使用XMLHttpRequest
发送请求时,浏览器发现该请求不符合同源策略,会给该请求加一个请求头:Origin
,后台进行一系列处理,如果确定接受请求则在返回结果中加入一个响应头:Access-Control-Allow-Origin
;浏览器判断该响应头中是否包含Origin
的值,如果有则浏览器会处理响应,我们就可以拿到响应数据,如果不包含,浏览器直接驳回,这时我们无法拿到响应数据。
整个CORS
通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS
通信与同源的ajax
通信没有差别,代码一样。浏览器一旦发现ajax
请求跨源,就会自动添加一些附加的请求头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS
的关键是服务器,只要服务器实现了CORS
接口,就可以跨源通信
<body>
<div class="container">
<ul class="news"></ul>
<button class="show">show news</button>
</div>
<script>
var ul = document.querySelector('.news');
var btn = document.querySelector('.show');
btn.addEventListener('click', function(){
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://127.0.0.1:8080/getNews', true);
xhr.send();
xhr.onload = function(){
appendHtml(JSON.parse(xhr.responseText))
}
})
function appendHtml(news){
var html = '';
for(var i = 0; i < news.length; i++){
html += '<li>' + news[i] + '</li>';
}
ul.innerHTML = html;
}
</script>
</body>
var http = require('http');
var fs = require('fs');
var path = require('path');
var url = require('url');
http.createServer(function(req, res){
var pathObj = url.parse(req.url, true);
switch(pathObj.pathname){
case '/getNews':
var news = [
'苹果',
'香蕉',
'猕猴桃'
];
res.setHeader('Content-type', 'text/json; charset=utf-8');
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080');
res.end(JSON.stringify(news));
break;
default:
fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
if(e){
res.writeHead(404, 'not found');
res.end('<h1>404 not found</h1>');
}else {
res.end(data);
}
})
}
}).listen(8080);
使用node.js
搭建服务器,请求如下所示:

若将
Access-Control-Allow-Origin
改为*
,则包含所有,任何网站都可以访问
3. 降域
例如:A
网站的a.html
访问B
网站的b.html
,A
网站的域名a.xxx.com:8080
,B
网站的域名b.xxx.com:8080
,a.html
上嵌入iframe
的b.html
。在同源策略下这两者之间域名不同是不能互相访问的,只有在同源的情况下,父窗口和子窗口才能通信;如果跨域,就无法拿到对方的DOM
。为达到目的,我们可以采取降域措施,将两者的域名设置为document.domain = 'xxx.com'
,完整的代码如下所示
a.html
:
<body>
<div class="ct">
<h1>使用降域实现跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.xxx.com:8080/a.html">
</div>
<iframe src="http://b.xxx.com:8080/b.html" frameborder="0"></iframe>
</div>
<script>
var input = document.querySelector('.main input');
input.addEventListener('input', function(){
console.log(this.value);
window.frames[0].document.querySelector('input').value = this.value;
});
document.domain = 'xxx.com';
</script>
</body>
b.html
:
<body>
<input id="input" type="text" placeholder="http://b.xxx.com:8080/b.html">
<script>
var input = document.querySelector('#input');
input.addEventListener('input', function(){
window.parent.document.querySelector('input').value = this.value;
});
document.domain = 'xxx.com';
</script>
</body>
为模拟两个不同域名下的页面互相访问,我们可在本地修改host
,修改如下:
127.0.0.1 a.xxx.com
192.168.1.23 b.xxx.com
因此在开始http-server
后,a.html
和b.html
分别对应:
127.0.0.1:8080/a.html a.xxx.com:8080/a.html
192.168.1.23:8080/b.html b.xxx.com:8080/b.html
当在父窗口的输入框中输入字符时,子窗口的输入框也会输入相同的内容,效果如图所示:

注:此方法只适用于一级域名相同,只是二级域名不同的两个窗口,把么就可设置document.domain
属性,规避同源策略
4. postMessage
仍以上个例子举例,父窗口a.xxx.com
向子窗口b.xxx.com
发消息,在不改变domain
的情况下,调用postMessage
方法即可
H5
引入新的API
:window.postMeaasge
,此方法提供了一种受控机制来规避同源策略的限制,只要正确的使用,就可以安全地实现跨源通信,无论这两个窗口是否同源。
语法:
otherWindow.postMessage(message, targetOrigin, [transfer])
otherWindow
:其他窗口的一个引用,比如iframe
的contentWindow
、执行window.open
返回的窗口对象、或者是命名过或数值索引的window.frames
message
:将要发送到其他window
的数据
targetOrigin
:通过窗口的origin
属性来指定哪些窗口能接收到消息事件,其值可以是字符串*
(表示无限制)或者一个URI
(协议+域名+端口)
transfer
(可选):是一串和message
同时传递的Transferable
对象。这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权
当在父窗口使用postMessage
向子窗口传递消息时,需在子窗口监听message
事件
window.addEventListener('message', receiveMessage, false);
function receiveMessage(event){
var origin = event.origin;
if(origin !== 'http://b.xxx.com:8080'){
return;
}else {
console.log(event.data);
}
}
message
事件的参数是事件对象event
,提供以下3个属性:
-
event.data
:消息内容 -
event.origin
:调用postMessage
时消息发送方窗口的origin
(协议+域名+端口) -
event.source
:对发送消息的窗口对象的引用
a.html
:
<body>
<div class="ct">
<h1>使用postMessage跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.xxx.com:8080/a.html">
</div>
<iframe src="http://b.xxx.com:8080" frameborder="0"></iframe>
</div>
<script>
var input = document.querySelector('.ct input');
input.addEventListener('input', function(){
window.frames[0].postMessage(this.value, '*');
console.log(this.value)
});
window.addEventListener('message', function(e){
input.value = e.data;
console.log(e.data);
});
</script>
</body>
b.html
:
<body>
<input id="input" type="text" placeholder="http://b.xxx.com:8080/b.html">
<script>
var input = document.querySelector('#input');
input.addEventListener('input', function(){
window.parent.postMessage(this.value, '*');
console.log(this.value)
});
window.addEventListener('message', function(e){
input.value = e.data;
console.log(e.data);
console.log(e.source);
console.log(e.origin);
});
</script>
</body>
当在父窗口的输入框中输入字符时,子窗口的输入框也输入相同的内容,如图所示:

打印如下:

总结:本文仅详述了4种跨域的方法,还有其他的可参考阮一峰 - 同源策略
网友评论