含义
同源策略是浏览器的一种安全策略,最早是由网景(Netscape)公司于1995年引入,最早的目的是保护用户在网站A的cookie不被其它网站访问到。同源的含义有三个方面:
- 协议相同
- 域名相同
- 端口相同
上面三个条件同时满足才被浏览器认为是同源,这里需要注意的有两部分:
- https与http是两个不同的协议
www.baidu.com和baidu.com是两个不同的域名
为了说明同源策略的必要性,下面举个例子来说明如果没有同源策略会发生什么可怕的事情:
- 你在社交网站A登录了你的账号
- 在没有退出登录的同时由于好奇登录了恶意网站B
- 由于没有同源策略,网站B可以随意获取你在网站A的cookie,并且利用这个cookie登录到网站A,开始对你的账号为所欲为
限制范围
随着互联网的发展,现在同源策略的限制范围已经从最初的cookie扩展到以下三个方面:
- 存储:cookie,localStorage,IndexDB
- DOM无法请求,这个主要是针对
iframe和window.open的多窗口通信 - 对于Ajax请求无法得到数据响应
解决方法
window.postMessage
window.postMessage是HTML5引入的API,解决的是窗口间跨域通信的问题,应用起来也非常简便。譬如网站A通过window.open打开网站B,那么只需要调用网站B对象的postMessage方法即可向网站B传递信息,网站B则监听message事件即可得到信息,具体代码如下: ```javascript // http://localhost:3000/index.html const newWindow = window.open(‘http://www.abc.com‘) newWindow.postMessage(‘msg from A’, ‘http://www.abc.com‘)
// http://localhost:4000/index.html window.addEventListener(‘message’, (e) => console.log(e.data))
message事件对应的event对象包含以下属性:- source:发送信息的窗口,监听器可以利用这个来向源窗口发送信息- origin:消息送往的地址,监听器根据这可以判断信息是否是传给自己的- data:发送的数据**优势**:窗口之间跨域通信的最佳方法,能传递各种数据类型,而且没有大小限制<a name="QbVil"></a>### document.domain这个是针对**两个一级域名、端口和协议相同的源**,可以通过设置 `document.domain` 来实现同源。譬如 `https://www.abc.com` 和 `https://abc.com` ,如果前者需要访问后者的**cookie或者DOM**,则可以设置 `document.domain = abc.com` 来实现同源,从而访问cookie或者DOM。优点:设置简单<br />缺点:适用范围过小,首先**只能获取cookie或者DOM**,其次对其他种类的不同源(如端口、协议、一级域名不同)就没有办法了。<a name="rhRBQ"></a>### hash + iframe这种方法针对的是窗口之间的跨域通信。URL后面有时候会跟一个 `#` ,这个 `#` 又名锚点,目的是跳转到网页的特定部分,修改这个部分不会引发网页刷新,但是触发 `window.onhashchange` 事件。利用这个特性,我们就可以实现不同源窗口的通信。****<br />原理如上图:<br />网页结构是 `a.html` 含有一个 `b.html` 的iframe, `b.html`含有一个 `c.html` 的iframe<br />我们的目标是实现 `a.html` 和 `b.html` 之间的相互通信。- `a.html` 向 `b.html` 发送信息(蓝色路径):则只需要 `a.html`将信息内容传入到其 `iframe.src` 的hash部分,其余部分保持不变- `b.html` 向 `a.html` 发送信息(红色路径):由于 `b.html` 不能直接修改非同源的父窗口的地址,所以需要先将信息传输给与 `a.html` 同源的 `c.html` ,然后再通过 `c.html` 传输给 `a.html`,具体方法是修改 `window.parent.parent.location.href` 值,其中 `window.parent.parent` 即为 `a.html` 的窗口。**优点:**能实现窗口间的**相互通信**<br />**局限**:1. 只能用于与iframe之间的窗口通信1. 浏览器对URL的长度有限制<a name="Wg3Sd"></a>### window.name + iframe这种方法主要是**利用iframe窗口即便发生跳转,其 `window.name` 也保持不变的性质**,适用于iframe窗口向父窗口传递信息的情况。<br />上图中, `http://localhost:3000/index.html` 有一个iframe窗口 `http://localhost:4000/index.html` ,我们想要实现的是**iframe窗口向父窗口传递信息**。于是有以下步骤:1. iframe窗口加载时通过 `window.name` 传入需要传递的信息1. **父窗口监听iframe窗口的onload事件**,等iframe窗口**首次加载完成后**修改其 `src` 属性,使其**跳转**到自身域下的任意一个页面,上面的例子是 `proxy.html` 。1. iframe窗口由于src被修改跳转至 `proxy.html` ,并通过onload事件通知父窗口1. 父窗口通过 `iframe.contentWindow.name` 得到iframe窗口传递的信息上面相关代码如下:<br />`http://localhost:3000/index.html````html<!doctype html><html lang="en"><head><meta charset="UTF-8"><title>A</title></head><body>A<iframe id="iframe" src="http://localhost:4000/index.html" frameborder="0" style="display: none"></iframe><script>const PROXY_ADDR = "http://localhost:3000/proxy.html"const element = document.querySelector('#iframe')element.addEventListener('load', () => {// 首次加载,跳转至本域的空白页面if (element.src !== PROXY_ADDR) {element.src = PROXY_ADDR} else {// 再次加载,读取消息console.log(element.contentWindow.name)}})</script></body></html>
http://localhost:4000/index.html
<!doctype html><html lang="en"><head><meta charset="UTF-8"><title>B</title></head><body><script>window.name = 'message from http://localhost:4000 !'</script></body></html>
JSONP
这种方法针对的是Ajax跨域,原理是利用script标签的跨域能力来向服务器请求数据,服务器将浏览器指定的回调函数包在数据外面,脚本下载完成后浏览器就能通过回调函数处理数据了。
JSONP的promise封装如下:
function jsonp(url) {return new Promise((resolve, reject) => {const script = document.createElement("script");const callbackName = `JSONCallBack${Math.random()}`; // 避免多个jsonp命名冲突window[callbackName] = (data) => {resolve(data);};script.src = `${url}?callback=${callbackName}`;script.onerror = (err) => {reject(err);};script.onload = () => { // 成功请求后自动移除script标签script.remove();};document.body.appendChild(script);});}
局限:
- 只能用于get请求
- 只能知道响应的成功或失败,无法知道状态码等等更多的信息
CORS
语雀内容
参考文章:
http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html
http://www.ruanyifeng.com/blog/2016/04/cors.html
