同源策略(Same origin policy)是由Netscape提出的一种安全策略约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。
定义与说明
定义
所谓同源是指,域名,协议,端口相同。当浏览器的某个页面执行或者请求某个资源或者脚本时,如果请求路径非同源,浏览器会在控制台中报告一个异常,提示拒绝访问。
目的
保证用户信息的安全,防止恶意的网站窃取数据。特别是对于存有大量用户个人信息的cookie
而言,如果其他网站可以随意访问,这将是极大的安全隐患。
限制范围
目前,如果非同源,共有三种行为受到限制:
- Cookie、LocalStorage 和 IndexDB 无法读取;
- DOM 无法获得;
- AJAX 请求不能发送。
优点与缺点
优点
- 阻止不可靠的数据源;
- 防止数据被恶意窃取;
- 防止数据被恶意篡改。
缺点
- 用户合理的跨域请求被拒绝;
- 对于前后端分离的应用开发,只能在服务器上进行调试。
跨域访问
Cookie
Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。
方法一
如果两个网页二级域名相同,只是三级域名不同,浏览器允许通过设置document.domain
共享 Cookie。
例如:a.huangyufeng.com
与b.huangyufeng.com
,只需将document.domain
设置成huangyufeng.com
即可共享同一个Cookie:1
document.domain = 'huangyufeng.com';
方法二
与方法一原理相同,只是不通过客户端设置domain
,而是在服务端设置Cookie值时添加对domain
的设定:1
Set-Cookie: key=value; domain=.huangyufeng.com; path=/
注:
doucument.domain
的值只能从tight
状态转向loose
状态的域名,即只能从长级域名转向相对短级的域名;- 这种方法只适用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 无法通过这种方法规避同源政策。
iframe
如果两个网页不同源,就无法拿到对方的DOM。典型的例子是iframe
窗口和window.open
方法打开的窗口,它们与父窗口无法通信。
对于iframe跨域,有四种方法:
- document.domain
如果两个窗口之间二级域名相同,而三级域名或更高级域名不同时,可以设置document.domain
来规避同源策略。同Cookie跨域中的方法一。 - 片段标识符(fragment identifier)
片段标识符(fragment identifier)指的是,URL的#号后面的部分,比如http://huangyufeng.com/#fragment
的#fragment
。如果只是改变片段标识符,页面不会重新刷新。
父窗口可以把信息写入子窗口的片段标识符:子窗口通过监听hashchange事件得到通知:1
2var src = originURL + '#' + data;
document.getElementById('myIFrame').src = src;同样的,子窗口也可以改变父窗口的片段标识符:1
2
3
4
5
6window.onhashchange = checkMessage;
function checkMessage() {
var message = window.location.hash;
// ...
}1
parent.location.href= target + "#" + hash;
- window.name
window.name
的最大特点是无论是否同源,在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name
的,每个页面对window.name
都有读写的权限,window.name
是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。
子窗口将数据写入window.name
:父窗口读取子窗口数据:1
window.name = 'data';
1
var data = document.getElementById('myFrame').contentWindow.name;
这种方法的优点是,window.name容量很大,可以放置非常长的字符串;缺点是必须监听子窗口window.name属性的变化,影响网页性能。
- 跨文档通信API(Cross-document messaging)
HTML5为了解决跨域问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。
- window.postMessage
API为window对象新增了一个window.postMessage
方法,允许跨窗口通信,不论这两个窗口是否同源:
发送数据:接收数据:1
window.postMessage(data, url); // url可为*
1
2
3
4
5
6window.addEventListener('message', function(e) {
console.log(e.data); // 接收到的数据
console.log(e.source); // 数据源(发送数据的window对象)
console.log(e.origin); // 数据目的地址,用于过滤发送给其他目标的数据
e.source.postMessage('newdata', '*'); // 向数据源传递数据
}, false); - LocalStorage
通过window.postMessage,读写其他窗口的 LocalStorage 也成为了可能:1
2
3
4
5
6
7window.onmessage = function(e) {
if (e.origin !== 'http://huangyufeng.com') {
return;
}
var payload = JSON.parse(e.data);
localStorage.setItem(payload.key, JSON.stringify(payload.data));
};
AJAX
同源政策规定,AJAX请求只能发给同源的网址,否则就报错。
除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),有三种方法规避这个限制:
- JSONP
在web中通过<script>
方式引入脚本的方式不受同源限制,因此,可以通过向web添加一个<script>
标签,向服务器请求JSON
数据,服务器收到请求后,将数据放在一个指定名字的回调函数里传回来:1
2
3
4
5
6
7
8var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = 'http://huangyufeng.com/ip?callback=foo'; // callback指定回调函数
document.body.appendChild(script);
function foo(data) {
data.attribute;
}传入参数为
JSON
对象,不需要转换。
JSONP只能适用于GET请求 - WebSocket
WebSocket请求头字段中有一个字段Origin
用于指定请求源,因此规避了同源策略。
详见:《计算机网络之WebSocket》 - CORS
CORS
是一个W3C标准,全称是跨域资源共享(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest
请求,从而克服了AJAX只能同源使用的限制。
详见:《同源策略规避之CORS》
补充
二级域名定义
在查阅各类资料时,我发现对于二级域名的定义出现了两种情况:
huangyufeng.com
为二级域名,.com
为顶级域名(一级域名),以此类推;huangyufeng.com
为一级域名,x.huangyufeng.com
为二级域名,以此类推。
查阅维基百科、ICCANWiki、ICCAN官方文档后可以认定,第一种理解方式是被标准认定的。