同源,何为同源,同源的意思就是协议、端口、域名三者均需要相同才能构成同源。例如 这个域名来说,https://为协议,www.oecom.cn为域名,默认的端口为80端口。
https://www.oecom.cn:8080 //端口不同,不同源
http://www.oecom.cn//协议不同,不同源
https://oecom.cn//域名不同,不同源
同源策略是浏览器的一个安全基石,保证用户信息的安全,防止恶意的网站窃取数据。
随着互联网的发展,"同源政策"越来越严格,基本上会有一下几种情况受到同源策略的制约
1 .Cookie、LocalStorage 和 IndexDB 无法读取。
2. DOM 无法获得。
3. AJAX 请求不能发送。
虽然这些限制是必要的,很好的防止数据被其他网站恶意窃取修改。但是合理的用途也会受到影响。
cookie是服务器端和浏览器端都可以读写的信息存储方式,只有同源的网页才可以共享。但是,两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain共享 Cookie。
例如:https://a.oecom.cn下有一个a.html,在https://b.oecom.cn下有一个b.html,这两个页面需要共享cookie,由于两个页面都是oecom.cn的二级域名,均可以采用设置document.domain的方式来进行跨域。
在js中加入document.domain='oecom.cn'
,这样就已经设置好了,这种方法适用于 Cookie 和 iframe 窗口。
当然也可以通过服务器端进行设置,这样在二级、三级域名下都可以共享cookie
Set-Cookie: key=value; domain=.example.com; path=/
利用document.domain 实现跨域有前提条件:这两个域名必须属于同一个基础域名!而且所用的协议,端口都要一致,否则无法利用document.domain进行跨域
如果两个网页不同源,就无法拿到对方的DOM。典型的例子是iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信。
比如,父窗口运行下面的命令,如果iframe窗口不是同源,就会报错
document.getElementById("myIFrame").contentWindow.document
// Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.
上面命令中,父窗口想获取子窗口的DOM,因为跨源导致报错。反之亦然,子窗口获取主窗口的DOM也会报错。
window.parent.document.body
// 报错
如果两个窗口一级域名相同,只是二级域名不同,那么设置上面所提到的document.domain属性,就可以规避同源政策,拿到DOM。
对于完全不同源的网站,目前有三种方法,可以解决跨域窗口的通信问题:
1. 片段识别符(fragment identifier)
2. window.name
3.跨文档通信API(Cross-document messaging)
片段识别符指的是#后面的部分,例如:https://www.oecom.cn/index.html#fragment,#fragment就是片段识别符,通过修改片段识别符不会导致刷新页面。
var src = originURL + '#' + data;//originURL为原ifream的地址,data为要传输的数据
document.getElementById('myIFrame').src = src;
子窗口可以通过监听hashchange事件来得到通知。
window.onhashchange = checkMessage;
function checkMessage() {
var message = window.location.hash;
// dosomething...
}
同样的,子窗口也可以改变父窗口的片段标识符。
parent.location.href= target + "#" + hash;//target为父窗口的原URL
浏览器窗口有一个属性叫做window.name,这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。父窗口先打开一个子窗口,载入一个不同源的网页,该网页将信息写入window.name属性:window.name = data。
接着,子窗口跳回一个与主窗口同域的网址。
location = 'http://parent.url.com/xxx.html';
然后,主窗口就可以读取子窗口的window.name了。
var data = document.getElementById('myFrame').contentWindow.name;
这种方法的优点是,window.name容量很大,可以放置非常长的字符串;缺点是必须监听子窗口window.name属性的变化,影响网页性能。
在HTML5中,为了解决跨域通信问题,提供了一个全新的API:即跨文档通信API。这个API为window对象增加了一个window.postMessage,它允许跨窗口通信,不局限于是否同源。
例如a窗口向b窗口发送消息
<div style="width:200px; float:left; margin-right:200px;border:solid 1px #333;">
<div id="color">Frame Color</div>
</div>
<div>
<iframe id="child" src="http://a.com/a.html"></iframe>
</div>
我们可以在http://test.com/index.html通过postMessage()方法向跨域的iframe页面http://a.com/a.html传递消息
window.onload=function(){
window.frames[0].postMessage('getcolor','http://a.com');
}
postMessage(data,origin)方法接受两个参数:
1. data:要传递的数据,html5规范中提到该参数可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候需要使用JSON.stringify()方法对对象参数序列化,在低版本IE中引用json2.js可以实现类似效果。
2. origin:字符串参数,指明目标窗口的源,协议+主机+端口号[+URL],URL会被忽略,所以可以不写,这个参数是为了安全考虑,postMessage()方法只会将message传递给指定窗口,当然如果愿意也可以建参数设置为"*",这样可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。
消息发送完成以后必须接收才能有意义,方法就是监听window的message事件。例如在http://a.com/a.html页面可以这样写
window.addEventListener('message',function(e){
if(e.source!=window.parent) return;//只接收父窗口的数据
var color=container.style.backgroundColor;
window.parent.postMessage(color,'*');
},false);
这样我们就可以接收任何窗口传递来的消息了。
通过postMessage来传输数据以后,极大的方便了数据的传输,子窗口接收到信息以后可以将信息存储到cookie或者是localStorage中,父窗口也可以将自己的cookie或localStorage中的数据传输到子窗口,这样变相达到了两者存储方式的跨域。
同源政策规定,AJAX请求只能发给同源的网址,否则就报错。除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),有三种方法规避这个限制:JSONP、WebSocket 、CORS。
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);
};
上面代码通过动态添加<script>元素,向服务器example.com发出请求。注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于JSONP是必需的。
服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。
foo({
"ip": "8.8.8.8"
});
由于<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了<foo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse的步骤。
WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。
下面是一个例子,浏览器发出的WebSocket请求的头信息
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
上面代码中,有一个字段是Origin,表示该请求的请求源(origin),即发自哪个域名。
正是因为有了Origin这个字段,所以WebSocket才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。相比JSONP只能发GET
请求,CORS允许任何类型的请求。