含义

同源策略浏览器的一种安全策略,最早是由网景(Netscape)公司于1995年引入,最早的目的是保护用户在网站A的cookie不被其它网站访问到。同源的含义有三个方面:

  1. 协议相同
  2. 域名相同
  3. 端口相同

上面三个条件同时满足才被浏览器认为是同源,这里需要注意的有两部分:

  1. https与http是两个不同的协议
  2. www.baidu.combaidu.com 是两个不同的域名

为了说明同源策略的必要性,下面举个例子来说明如果没有同源策略会发生什么可怕的事情:

  1. 你在社交网站A登录了你的账号
  2. 在没有退出登录的同时由于好奇登录了恶意网站B
  3. 由于没有同源策略,网站B可以随意获取你在网站A的cookie,并且利用这个cookie登录到网站A,开始对你的账号为所欲为

限制范围

随着互联网的发展,现在同源策略的限制范围已经从最初的cookie扩展到以下三个方面:

  1. 存储:cookielocalStorage,IndexDB
  2. DOM无法请求,这个主要是针对 iframewindow.open 的多窗口通信
  3. 对于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))

  1. message事件对应的event对象包含以下属性:
  2. - source:发送信息的窗口,监听器可以利用这个来向源窗口发送信息
  3. - origin:消息送往的地址,监听器根据这可以判断信息是否是传给自己的
  4. - data:发送的数据
  5. **优势**:窗口之间跨域通信的最佳方法,能传递各种数据类型,而且没有大小限制
  6. <a name="QbVil"></a>
  7. ### document.domain
  8. 这个是针对**两个一级域名、端口和协议相同的源**,可以通过设置 `document.domain` 来实现同源。譬如 `https://www.abc.com` `https://abc.com` ,如果前者需要访问后者的**cookie或者DOM**,则可以设置 `document.domain = abc.com` 来实现同源,从而访问cookie或者DOM
  9. 优点:设置简单<br />缺点:适用范围过小,首先**只能获取cookie或者DOM**,其次对其他种类的不同源(如端口、协议、一级域名不同)就没有办法了。
  10. <a name="rhRBQ"></a>
  11. ### hash + iframe
  12. 这种方法针对的是窗口之间的跨域通信。URL后面有时候会跟一个 `#` ,这个 `#` 又名锚点,目的是跳转到网页的特定部分,修改这个部分不会引发网页刷新,但是触发 `window.onhashchange` 事件。利用这个特性,我们就可以实现不同源窗口的通信。
  13. **![image.png](https://cdn.nlark.com/yuque/0/2020/png/1431836/1605434099439-7bf74cc8-a653-407e-bf7e-bfcd5526a677.png#align=left&display=inline&height=396&margin=%5Bobject%20Object%5D&name=image.png&originHeight=584&originWidth=852&size=44291&status=done&style=none&width=577)**<br />原理如上图:<br />网页结构是 `a.html` 含有一个 `b.html` 的iframe, `b.html`含有一个 `c.html` 的iframe<br />我们的目标是实现 `a.html` 和 `b.html` 之间的相互通信。
  14. - `a.html` `b.html` 发送信息(蓝色路径):则只需要 `a.html`将信息内容传入到其 `iframe.src` hash部分,其余部分保持不变
  15. - `b.html` `a.html` 发送信息(红色路径):由于 `b.html` 不能直接修改非同源的父窗口的地址,所以需要先将信息传输给与 `a.html` 同源的 `c.html` ,然后再通过 `c.html` 传输给 `a.html`,具体方法是修改 `window.parent.parent.location.href` 值,其中 `window.parent.parent` 即为 `a.html` 的窗口。
  16. **优点:**能实现窗口间的**相互通信**<br />**局限**:
  17. 1. 只能用于与iframe之间的窗口通信
  18. 1. 浏览器对URL的长度有限制
  19. <a name="Wg3Sd"></a>
  20. ### window.name + iframe
  21. 这种方法主要是**利用iframe窗口即便发生跳转,其 `window.name` 也保持不变的性质**,适用于iframe窗口向父窗口传递信息的情况。
  22. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/1431836/1605431861445-b09e5ca3-c084-4369-b1a0-72254819656c.png#align=left&display=inline&height=331&margin=%5Bobject%20Object%5D&name=image.png&originHeight=532&originWidth=1038&size=47394&status=done&style=none&width=645)<br />上图中, `http://localhost:3000/index.html` 有一个iframe窗口 `http://localhost:4000/index.html` ,我们想要实现的是**iframe窗口向父窗口传递信息**。于是有以下步骤:
  23. 1. iframe窗口加载时通过 `window.name` 传入需要传递的信息
  24. 1. **父窗口监听iframe窗口的onload事件**,等iframe窗口**首次加载完成后**修改其 `src` 属性,使其**跳转**到自身域下的任意一个页面,上面的例子是 `proxy.html`
  25. 1. iframe窗口由于src被修改跳转至 `proxy.html` ,并通过onload事件通知父窗口
  26. 1. 父窗口通过 `iframe.contentWindow.name` 得到iframe窗口传递的信息
  27. 上面相关代码如下:<br />`http://localhost:3000/index.html`
  28. ```html
  29. <!doctype html>
  30. <html lang="en">
  31. <head>
  32. <meta charset="UTF-8">
  33. <title>A</title>
  34. </head>
  35. <body>
  36. A
  37. <iframe id="iframe" src="http://localhost:4000/index.html" frameborder="0" style="display: none"></iframe>
  38. <script>
  39. const PROXY_ADDR = "http://localhost:3000/proxy.html"
  40. const element = document.querySelector('#iframe')
  41. element.addEventListener('load', () => {
  42. // 首次加载,跳转至本域的空白页面
  43. if (element.src !== PROXY_ADDR) {
  44. element.src = PROXY_ADDR
  45. } else {
  46. // 再次加载,读取消息
  47. console.log(element.contentWindow.name)
  48. }
  49. })
  50. </script>
  51. </body>
  52. </html>

http://localhost:4000/index.html

  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>B</title>
  6. </head>
  7. <body>
  8. <script>window.name = 'message from http://localhost:4000 !'</script>
  9. </body>
  10. </html>

局限:信息只能从iframe窗口传向父窗口

JSONP

这种方法针对的是Ajax跨域,原理是利用script标签的跨域能力来向服务器请求数据,服务器将浏览器指定的回调函数包在数据外面,脚本下载完成后浏览器就能通过回调函数处理数据了。

JSONP的promise封装如下:

  1. function jsonp(url) {
  2. return new Promise((resolve, reject) => {
  3. const script = document.createElement("script");
  4. const callbackName = `JSONCallBack${Math.random()}`; // 避免多个jsonp命名冲突
  5. window[callbackName] = (data) => {
  6. resolve(data);
  7. };
  8. script.src = `${url}?callback=${callbackName}`;
  9. script.onerror = (err) => {
  10. reject(err);
  11. };
  12. script.onload = () => { // 成功请求后自动移除script标签
  13. script.remove();
  14. };
  15. document.body.appendChild(script);
  16. });
  17. }

局限

  1. 只能用于get请求
  2. 只能知道响应的成功或失败,无法知道状态码等等更多的信息

    CORS

    语雀内容

参考文章:
http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html
http://www.ruanyifeng.com/blog/2016/04/cors.html