相关文章推荐
不要命的菠萝  ·  fortran 矩阵乘法-掘金·  2 年前    · 
飘逸的蘑菇  ·  java - Is ...·  2 年前    · 
爱健身的剪刀  ·  linq select max date ...·  3 年前    · 

跨域总结 #199

@yaofly2012

Description

一、何为跨域

源(Origin):协议+主域+端口;
同源(Same Origin):协议,域名,端口三者都相同视为同源;
跨域:是指资源的源和文档的源不同。

二、受跨域影响资源

2.1 不受同源策略限制的

  • 跨域资源写入:
  • <script>
  • <iframe>
  • <video>
  • Form 表单提交。
  • 2.2 受同源策略限制范围

    跨域脚本API访问(不同源的DOM操作);

    跨域数据存储访问:Cookie,Storage,(?IndexedDB,ApplicationCache,CacheStorage);

    JS Http请求( XMLHttpRequest , fetch );

    @font-face ;
    字体是版权比较敏感的资源。

  • 跨文档DOM API访问
    JS中可以通过 iframe.contentWindow , window.open , window.opener , window.parent 等API进行文档间的交互。但这只限于同源文档之间,非同源之间不能交互。
  • 跨文档数据访问
    除了DOM API外,还有其他 localStorage sessionStorage 等本地缓存数据也受同源限制。
    本质上 跨文档DOM API访问目的是为了数据交换,所以也属于跨文档数据访问
  • 页面URL: http://localhost:8082/crossOrigin/home.html

    <!DOCTYPE html>
            <title>Home</title>
        </head>
                <h1>Home</h1>
            </div>
            <iframe id="iframe" src="http://127.0.0.1:8082/crossOrigin/detail.html" height="200" width="300"></iframe>
            <script>
                ;(function() {
                    var iframe = document.getElementById('iframe');
                    debugger
                    var title = iframe.contentWindow.document.title;
                    console.log(`title=${title}`);
                })();
            </script>
        </body>
    </html>

    跨域情况下除了 postMessage 函数外,不可以访问 contentWindow 对象的任何属性。

    3.2 解决方案 window.name

    可用于页面跳转时的跨文档数据通信。
    Home页面

    <!DOCTYPE html>
            <title>Home</title>
        </head>
                <h1>Home</h1>
                <button id="exchange">Set window.name</button>
                <a href="http://127.0.0.1:8082/crossOrigin/detail.html">Detail</a>
            </div>
            <!-- <iframe id="iframe" src="http://127.0.0.1:8082/crossOrigin/detail.html" height="200" width="300"></iframe> -->
            <script>
                ;(function() {
                    document.getElementById('exchange').onclick = () => {
                        window.name = `Home: ${Math.random()}`
                })();
            </script>
        </body>
    </html>

    Detail页面:

    <!DOCTYPE html>
            <title>Detail</title>
        </head>
                <h1>Detail</h1>            
            </div>
            <script>
                ;(() => {
                    console.log(`detail: ${window.name}`);
                })()
            </script>
        </body>
    </html>
  • window.name 可以保存大数据;
    之前看有框架在浏览器无痕模式下把本地缓存数据(MemoryStorage)都保存在 window.name 里。
  • function isPrivateModel() {
      var testKey = "TEST_PRIVATE_MODEL_KEY";
      var valueExpire = "TEST_PRIVATE_MODEL_VALUE";
      var valueActual;
      var storage = window.localStorage;
      try {
        storage.setItem(testKey, valueExpire);
    
    
    
    
        
    
        valueActual = storage.getItem(testKey);
        storage.removeItem(testKey);
      } catch(e){
        // QuotaExceededError: DOM Exception 22
        return true;
      //UC隐私模式下testValue !== value
      return valueActual !== valueExpire;  
    

    只能保存字符串数据;
    一般是把对象转成JSON字符串保存。

    文档和内嵌iframe之前无法使用window.name进行通信,因为两个文档的全局对象window是不同的

    3.3 终极解决方案Window.postMessage()

    HTML5引入的API专门用于解决跨域文档之间的通讯,本质是跨域文档之间的window对象之间通讯:

    enables cross-origin communication between Window objects; e.g., between a page and a pop-up that it spawned, or between a page and an iframe embedded within it.

    只适用于:

  • 文档和文档内嵌iframe
  • HTMLIFrameElement.contentWindow
  • window.parent
  • 文档和文档打开的文档。
  • window.open
  • window.opener
  • // 发消息
    targetWindow.postMessage(message, targetOrigin, [transfer]);
    // 收消息
    targetWindow.addEventListener("message", (event) => {
      // ...
    }, false);
  • postMessage是唯一可以跨域文档访问的API(必须得特殊处理,要不然怎么通讯呢)。
  • message & 结构化克隆算法

  • 传递的数据利用的是 结构化克隆算法复制数据。
    并不是任意数据都可以传递的,具体见Things that don't work with structured clone
  • DOM对象;
  • 正则对象的lastIndex属性。
  • 还可以利用结构化克隆算法实现深拷贝
  • targetOrigin

    指定目标window的origin

  • targetOrigin除了可以是origin外还可以是个URL,此时浏览器自动获取URL里的origin(不是直接使用URL匹配)。
  • targetOrigin默认值通配符"*",只有同域时使用通配符才能正常发消息;
  • 跨域时只有当targetOrigin指定的origin和当目标window的origin匹配时(大小写不敏感)才能发消息;
  • 收消息的时候最好在事件处理函数里增加origin白名单,即只处理白名单origin发的消息。
  • window.addEventListener("message", (event) => {
      if (event.origin !== "http://example.org:8080")
        return;
      // ...
    }, false);

    Transferable

    代表一个能在不同可执行上下文之间,列如主线程和Worker之间,相互传递的对象。
    主要用于管道通讯中转移MessagePort等。

    MessageEvent

    比较重要的属性:

  • origin
  • source
    消息发送者对象。可以是window, ServiceWorder, MessagePort。
  • ports
  • MessageChannel

    MessageChannelWindow.postMessage()背后的原理。
    利用MessageChannel可以自定义管道通讯。

    MessagePort必须调用start方法才能发消息;
    通过MessagePort.onmessage绑定事件时会内部调用start方法。利用EventTarget.addEventListener绑定事件需要手动调用start方法。

    window.postMessage内部也利用MessageChannel通讯,不过增加了origin的判断。

    window.postMessageMessageChannel除了用于管道通讯外,还有一些其他使用场景。

  • 作为setImmediate的polyfill
  • 四、客户端和跨域服务端通讯

    4.1 jsonp

    4.3 CORS