浏览器安全的基石是“同源政策”(same-origin policy)。
1995年,同源政策由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个政策。
何为同源?
- 协议相同
- 域名相同
- 端口相同
限制范围
- Cookie、LocalStorage 和 IndexedDB 无法读取。
- DOM 无法获得。
- AJAX 请求不能发送。
PS:Form表单提交不受同源政策限制;
一级域名相同,二级域名不同网页Cookie 和 iframe规避同源政策
一级域名相同,只是二级域名不相同的2个网页(A页和B页)间通信(共享cookie和ifrmame DOM):
//A网页通过脚本设置一个 Cookie document.cookie = "test1=hello"; //B网页不能读取 var allCookie = document.cookie; //父窗口不能读取子iframe的DOM对象,报错 document.getElementById("myIFrame").contentWindow.document //同样子窗口不能获取父窗口的元素,报错 window.parent.document.body
如果两个窗口一级域名相同,只是二级域名不同,那么设置document.domain属性,就可以规避同源政策,共享cookie和获取DOM,解决上面的问题:
//设置为一级域名,规避同源政策 document.domain = 'example.com';
完全不同源网站解决跨域窗口间通信问题
有2种方法:
- 片段识别符(fragment identifier)
- 跨文档通信API(Cross-document messaging)
片段识别符(fragment identifier),指的是URL的#号后面的部分,将此作为两个窗口通信内容的中介存储,可互为读取,实现通信。
父窗口把信息写入子窗口的片段识别符:
var src = originURL + '#' + data; document.getElementById('myIFrame').src = src;
子窗口通过监听hashchange事件,得到通知:
window.onhashchange = checkMessage; function checkMessage() { var message = window.location.hash; // ... }
同样,子窗口也可以改变父窗口的片段识别符:
parent.location.href= target + “#” + hash;
上面两种方法都属于破解,HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。
这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源:
父窗口向子窗口发送消息:
var popup = window.open('http://bbb.com', 'title'); popup.postMessage('Hello World!', 'http://bbb.com');
子窗口向父窗口发送消息:
window.opener.postMessage('Nice to see you', 'http://aaa.com');
postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即“协议 + 域名 + 端口”。也可以设为*,表示不限制域名,向所有窗口发送。
父窗口或子窗口监听对方发来的消息:
window.addEventListener('message', function(e) { console.log(e.data); },false);
message事件的事件对象提供3个属性:
- event.source:发送消息的窗口
- event.origin: 消息发向的网址
- event.data: 消息内容
子窗口通过event.source属性引用父窗口,然后回复消息:
window.addEventListener('message', receiveMessage); function receiveMessage(event) { event.source.postMessage('Nice to see you!', '*'); }
event.origin属性可以过滤不是发给本窗口的消息:
window.addEventListener('message', receiveMessage); function receiveMessage(event) { if (event.origin !== 'http://aaa.com') return; if (event.data === 'Hello World') { event.source.postMessage('Hello', event.origin); } else { console.log(event.data); } }
一个跨文档通信API案例:
//父窗口发送消息的代码 var win = document.getElementsByTagName('iframe')[0].contentWindow; var obj = { name: 'Jack' }; win.postMessage(JSON.stringify({key: 'storage', data: obj}), 'http://bbb.com'); //子窗口接收消息,写入localStorage window.onmessage = function(e) { if (e.origin !== 'http://bbb.com') { return; } var payload = JSON.parse(e.data); localStorage.setItem(payload.key, JSON.stringify(payload.data)); };
AJAX与同源政策
同源政策规定,AJAX请求只能发给同源的网址,否则就报错。
AJAX有3种方法规避同源政策:
- JSONP
- WebSocket
- CORS
第1种:JSONP
JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。
它的基本思想是,网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据以一个指定名字的回调函数的参数形式传回客户端,客户端在约定名字的回调函数中处理接收到的参数 。
首先,网页动态插入<script>元素,由它向跨源网址发出请求。
function addScriptTag(src) { var script = document.createElement('script'); script.setAttribute("type","text/javascript"); script.src = src; document.body.appendChild(script); } window.onload = function () { addScriptTag('http://example.com/ip?callback=foo'); } function foo(data) { console.log('Your public IP address is: ' + data.ip); };
服务器收到这个请求以后,会将数据放在回调函数的参数位置返回:
foo({ "ip": "8.8.8.8" });
由于<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串,所以不用JSON.parse处理,直接作为JSON对象使用。
参考
同源政策,阮一峰 http://javascript.ruanyifeng.com/bom/same-origin.html