浏览器的同源策略一直是开发中经常遇到的问题,同时也是面试中面试官经常提到的。但是一直也没有完全弄懂这个知识点。得此机会,全面总结一下,这一方面。
一、什么是浏览器的同源策略
同源
是指同协议
、同域名
、同端口
。
注:IE 未将端口号加入到同源策略的组成部分之中
浏览器同源策略的目的是为了保证用户信息的安全,防止恶意的网站窃取数据。如果网页之间不满足同源
要求,将不能:
- 共享Cookie、LocalStorage、IndexDB
- 获取DOM
- AJAX请求不能发送
二、既然知道这一策略,开发中如何规避呢?
(1)通过设置window.domain
的方式实现共享cookie
这种方法适用于一级域名相同,二级域名不同的时候使用
例如:现在有www.example.com
和example.com
两个网页,可以通过设置window.domain
的形式来处理,前端页面可以通过设置window.domain=example.com
或者后台设置Set-Cookie: key=value; domain=.example.com; path=/
来实现共享cookie
(2)通过修改片段标识符的方法实现跨域
这种方法适用于iframe嵌套网页间的跨域。片段标识符就是URL的#号后面的部分。改变片段标识符,页面不会刷新。
父窗口向子窗口传递信息
//父窗口
var src = originURL + '#' + data;
document.getElementById('myIFrame').src = src;
//子窗口通过监听hashchange事件回去父窗口传递的信息
window.onhashchange = checkMessage;
function checkMessage() {
var message = window.location.hash;
// ...
}
子窗口向父窗口传递信息
//子窗口修改父窗口的片段标识符,同样,父窗口也可以通过hashchange事件来获取数据
parent.location.href= target + "#" + hash;
(3)使用window.name处理跨域问题
window.name
属性具有如下特点,无论是否同源,只要在同一窗口中设置了该属性,后一个网页就可以读取到。该方法借助window.name
这一属性和iframe具有跨域能力的关键点,来处理跨域问题。
首先,在localhost:8080
服务器创建文件demo.html
和空白文件proxy.html
,然后,在localhost:8081
服务器创建data.html
文件,文件内容如下所示:
//localhost:8080/demo.html
<!doctype html>
<html>
<head>
<title>demo</title>
</head>
<body>
<h1>demo</h1>
<iframe id="myIframe" width="400" height="400"></iframe>
<script>
var myIframe = document.getElementById('myIframe');
myIframe.src = 'http://localhost:8081/yzdj-mobile-web/data.html';
var state = 0;
myIframe.onload = function(){
if(state === 1) {
console.log(myIframe.contentWindow.name);
myIframe.contentWindow.document.write('');
myIframe.contentWindow.close();
}else if(state === 0){
state = 1;
myIframe.contentWindow.location = 'http://localhost:8080/yzdj-admin-web/proxy.html';
}
}
</script>
</body>
</html>
//localhost:8081/data.html
<!doctype html>
<html>
<head>
<title>data</title>
</head>
<body>
<h1>data</h1>
<script type="text/javascript">
window.name = '{a:1,b:2}';
</script>
</body>
</html>
这种解决方法的优点是window.name容量大,缺点是:需要监听window.name变化,影响网页性能。
(4)使用window.postMessage
解决跨域
这种方法借助HTML5的跨文档API(cross-document messaging)中增加的window.postMessage
方法,允许跨窗口通信,不管窗口是否同源。父窗口和子窗口可以监听message
事件,获取对方发送的消息。这种方法可以实现LocalStorage
等信息的共享。
message
的event
对象提供三个属性:
- event.source:发送消息的窗口
- event.origin: 消息发向的网址
- event.data: 消息内容
下面分别使用window.open的方法和嵌套iframe的方法实现父子窗口之间的通信
使用window.open方法父页面:
//localhost:8080
<!doctype html>
<html>
<head>
<title>admin</title>
</head>
<body>
<h1 class="header">parent</h1>
<div class="mb20">
<textarea name="ta" id="data" cols="30" rows="5">hello world</textarea>
<button style="font-size:20px;" onclick="send()">post message</button>
</div>
<script>
var pop = window.open('http://localhost:8181/yzdj-mobile-web/demo.html');
function send() {
var data = document.querySelector('#data').value;
pop.postMessage(data, 'http://localhost:8181/'); // 触发跨域子页面的messag事件
}
window.addEventListener('message', function(messageEvent) {
var data = messageEvent.data;
console.info('message from child:', data);
}, false);
</script>
</body>
</html>
子页面:
//localhost:8081
<!doctype html>
<html>
<head>
<title>mobile</title>
</head>
<body>
<h1 class="header">chidren</h1>
<input type="text" id="inp" value="some contents..">
<button onclick="send()">send</button>
<script>
var origin ='';
var source = '';
window.addEventListener('message', function(ev) {
var data = ev.data;
origin = ev.origin;
source = ev.source
console.info('message from parent:', data);
}, false);
function send() {
var data = document.querySelector('#inp').value;
source.postMessage(data, origin); // 若父页面的域名和指定的不一致,则postMessage失败
}
</script>
</body>
</html>
使用iframe嵌套的方法,父页面
<!doctype html>
<html>
<head>
<title>parent</title>
</head>
<body>
<h1 class="header">parent</h1>
<div class="mb20">
<textarea name="ta" id="data" cols="30" rows="5">hello world</textarea>
<button style="font-size:20px;" onclick="send()">post message</button>
</div>
<!-- 跨域的情况 -->
<iframe src="http://localhost:8081/b.html" id="child" style="display: block; border: 1px dashed #ccc; height: 300px;"></iframe>
<script>
function send() {
var data = document.querySelector('#data').value;
window.frames[0].postMessage(data, 'http://localhost:9022/'); // 触发跨域子页面的messag事件
}
window.addEventListener('message', function(messageEvent) {
var data = messageEvent.data;
console.info('message from child:', data);
}, false);
</script>
</body>
</html>
子页面
<!doctype html>
<html>
<head>
<title>chilren</title>
</head>
<body>
<h1 class="header">chilren</h1>
<input type="text" id="inp" value="some contents..">
<button onclick="send()">send</button>
<script>
window.addEventListener('message', function(ev) {
var data = ev.data;
console.info('message from parent:', data);
}, false);
function send() {
var data = document.querySelector('#inp').value;
parent.postMessage(data, 'http://localhost:9011/'); // 若父页面的域名和指定的不一致,则postMessage失败
}
</script>
</body>
</html>
(5)使用JSONP来实现AJAX的跨域
实现JSONP可以通过向HTML中添加script标签,由它向网址发送请求
$('body').append('http://www.example.com/getData?callback=foo');
fucntion foo(data){
}
或者使用jq的ajax方法
$.ajax({
url:'http://www.example.com/getData',
dataType:'jsonp',
jsonp:'callback',
data:{
wd:'XX.value'
},
success:function(result){
alert(result.s);
} ,
error:function(err){
alert(err)
}
});
这种方法需要和设置修改请求网址的后台,代码中的callback需要和后台设置的一致
(6)webSocket
是一种通信协议,使用ws://
和wss://
作为协议前缀,这种协议不受同源政策的影响,只要服务器支持就可以。
(7)CORS(Cross-Origin Resource Sharing,跨源资源分享)
它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。这种解决跨域方法的优点是允许任何形式的请求,而JSONP只能发GET请求。
实现CORS通信的关键是服务器,只要服务器实现了CORS接口,就可跨源通信
简单请求(simple request)和非简单请求(not-so-simple request)
(1) 请求方法是以下三种方法之一:
HEAD
GET
POST
(2)HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
不同时满足上面条件的就是非简单请求。
对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段,用来说明本次请求的是哪个源。服务器根据这个字段来判断是否允许请求。
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
对于非简单请求,会在正式通信之前,增加一次HTTP查询请求,称为预检
(preflight)请求。
服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。
网友评论