浅谈浏览器「同源策略」
什么是同源政策 (Same Origin Policy)
「同源策略」指只有相同来源的的资源才能交互,限制跨来源资源的交互。
这是浏览器一个用于隔离潜在恶意文件的重要安全机制。
协议、域名以及端口三个完全一致才属于同源,其中一个不一致即非同源。
如果非同源,则有三种行为会受到限制。
1) Cookie、LocalStorage 和 IndexDB 无法读取。
2) DOM 无法获得。
3) XMLHttpRequest 或是 Fetch API 请求不能发送。
如何进行跨域通信
因为同源策略的限制,我们无法跨域通信。但在实际场景中我们可能有跨域通信的需求,这就需要我们去解决跨域通信问题了。
iframe
2-1-1 hash(片段标识符)
任一带#的URL称为片段URL。#左边部分是浏览器可以下载的资源,#右边部分称为片段标识符,表示资源内的某一位置。hash不会随HTTP请求发送,修改hash值不会重新加载页面。
// 比如
`http://example.com/x.html#fragment`
// #fragment 就是片段标识符
// 如何使用
// 利用hash,场景是当前页面 A 通过iframe或frame嵌入了跨域的页面 B
// 在父窗口A中伪代码如下:
var B = document.getElementsByTagName('iframe');
B.src = B.src + '#' + 'data';
// 在子窗口B中的伪代码如下
window.onhashchange = function () {
var data = window.location.hash;
};
2-1-2 使用 window.name
浏览器窗口有window.name属性。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。
// 场景是父窗口 A 通过iframe或frame嵌入了跨域的页面 B
<iframe src="B.html" ></iframe>
var ifra = document.querySelector('iframe')
// B 中将信息写入window.name属性。
window.name = JSON.stringify(data)
// 接着,父窗口 A 的iframe窗口跳回一个与主窗口 A 同域的网址。
// 最好是空页面 C ,减少加载资源。
ifra.contentWindow.location = '与主窗口 A 同域的网址';
// 父窗口 A 就可以读取子窗口的window.name。
var data = ifra.contentWindow.name;
2-1-3 使用window.postMessage
// 窗口A(http://A.com)向跨域的窗口B(http://B.com)发送信息
// 场景是父窗口 A 通过iframe或frame嵌入了跨域的页面 B
// 窗口A
window.postMessage('data...', 'http://B.com');
// 在窗口B中监听
window.addEventListener('message', function (event) {
if (event.origin !== 'http://A.com') return;
console.log(event.origin);
console.log(event.source);
console.log(event.data);
}, false);
// 反之亦然
XMLHttpRequest/Fetch
同源政策限制了XMLHttpRequest 和 Fetch Api,此时前后端如何进行交互?
2-2-1 JSONP
JSONP是利用script标签src属性不受同源策略限制,可以跨域引用文件的能力。
具体步骤:全局环境下声明一个函数,动态创建script标签,src指定要访问的接口并指定callback名称与声明的函数名称一致,动态添加script标签,利用src属性发送跨域请求。
function createScript (url, charset) {
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
charset && script.setAttribute('charset', charset);
script.setAttribute('src', url);
script.async = true;
return script;
};
function jsonp (url, callbackName, onsuccess, onerror, charset) {
window[callbackName] = function () {
if (onsuccess) {
onsuccess(arguments[0]);
}
};
var script = createScript(url + '&callback=' + callbackName, charset);
script.onload = script.onreadystatechange = function () {
if (!script.readyState || /loaded|complete/.test(script.readyState)) {
script.onload = script.onreadystatechange = null;
// 移除该script的 DOM 对象
if (script.parentNode) {
script.parentNode.removeChild(script);
}
// 删除函数或变量
window[callbackName] = null;
}
};
script.onerror = function () {
if (onerror) {
onerror();
}
};
document.getElementsByTagName('head')[0].appendChild(script);
};
2-2-2 websocket
WebSocket是一种双向实时通信,没有同源限制,客户端可以与任意服务器通信。
var ws = new WebSocket("wss://echo.websocket.org");
ws.onopen = function(evt) {
console.log("Connection open ...");
ws.send("Hello WebSockets!");
};
ws.onmessage = function(evt) {
console.log( "Received Message: " + evt.data);
ws.close();
};
ws.onclose = function(evt) {
console.log("Connection closed.");
};
2-2-3 CORS
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。对于客户端而言,CORS通信与同源的AJAX通信没有差别,代码完全一样。只要服务器实现了CORS接口,就可以跨源通信。
参考资料