前端跨域常用方法小結

Yeaseon_Zhang發表於2017-10-12

作者: Yeaseon
Blog:yeaseonzhang.github.io
原文連結

在開發過程中經常會涉及跨域問題,解決跨域問題的方案也有很多種,接下來就來梳理一下前端跨域的常用方法。

同源策略

何為跨域跨域是相對於同源而言。協議、域名和埠均相同,則為同源
瀏覽器通過同源策略限制從一個源載入的文件或指令碼與來自另一個源的資源進行互動。這是一個用於隔離潛在惡意檔案的關鍵的安全機制,摘抄自MDN

常見解決方案

document.domain

這種方案主要用於主域相同,子域不同的跨域情況。例如: https://jdc.jd.com/https://www.jd.com/

通過在www.jd.com/開啟一個jdc.jd.com/,此時JDC的域名是jdc.jd.com/,通過控制檯執行document.domain = 'jd.com';。強制設定主域,實現同源。

var jdc = window.open('https://jdc.jd.com/');
// JDC 頁面載入完成後執行
var divs = jdc.document.getElementsByTagName('div');

$(divs).css('border', '1px solid red');複製程式碼

通常的做法是通過iframe載入一個跨域頁面資源。因為window.open這個方法在瀏覽器中會被當做談廣告禁止掉。

domain.com/index.html

<iframe id="sub" src="http://sub.domain.com/index.html"></iframe>
<script>
  var username = 'yeseonzhang';
</script>複製程式碼

sub.domain.com/index.html

<script>
  document.domain = 'domain.com';
  console.log(window.parent.username);
</script>複製程式碼

location.hash

這種跨域方法主要是通過設定/監聽url的hash部分,來實現跨域,同時需要藉助第三個頁面來進行輔助。

上圖就是三個頁面的包含關係,以及hash的傳遞過程。

domain-a.com/a.html

<iframe id="iframe-b" src="http://domain-b.com/b.html"></iframe>
<script>
  var bPage = document.getElementById('iframe-b');

  /* step 1 */
  bPage.src = bPage.src + '#user=yeaseonzhang';

  function cb (res) {
    console.log(res);
  }
</script>複製程式碼

domain-b.com/b.html

<iframe id="iframe-c" src="http://domain-a.com/c.html"></iframe>
<script>
  var cPage = document.getElementById('iframe-c');

  window.onhashchange = function () {
    /* step 2 */
    cPage.src = cPage.src + location.hash;
  }
</script>複製程式碼

domain-a.com/c.html

<script>
  window.onhashchange = function () {
    /* step 3 */
     window.parent.parent.cb('success: ' + location.hash);
  }
</script>複製程式碼

由於a頁面c頁面是同域資源,所以c頁面可以通過window.parent.parent訪問a頁面資源。

window.name

這個方案類似location.hash,需要通過第三個頁面進行輔助。
window.name屬性是用來獲取/設定視窗的名稱。需要注意的是,當前視窗的window.name並不會因為頁面的過載和跳轉而更改,所以可以利用這個特性將跨域的window.name通過重定向到同域頁面進行讀取。

domain-a.com/a.html

<script>
  var iframe = document.createElement('iframe');
  /* step 1 載入跨域頁面 */
  iframe.src = 'http://domain-b.com/b.html';
  var domain = 'diff';

  /* 監聽iframe載入 */
  iframe.onload = function () {
    if ('diff' == domain) {
      /* step 2 重定向到同域頁面 */
      iframe.contentWindow.location = 'http://www.domain-a.com/c.html';
      domain = 'same';
    } else if ('same' == domain) {
      /* 獲取同域資源的window.name資訊 */
      cb(iframe.contentWindow.name);
      /* 清空資料 */
      iframe.contentWindow.name = '';
    }
  }

  function cb (res) {
    console.log(JSON.parse(res));
  }
</script>複製程式碼

domain-b.com/b.html

<scirpt>
  /* 寫入相關資料 */
  var obj = {
    username: 'yeaseonzhang'
  }
  window.name = JSON.stringify(obj);
</script>複製程式碼

domain-a.com/c.html

同域c頁面,可以是一個空頁面,不需要進行任何操作。

JSONP

JSONP(JSON with Padding)是JSON的一種使用方式。這種方式允許使用者傳遞一個callback引數給服務端,然後服務端返回資料時會將這個callback引數作為函式名來包裹住JSON資料。

眾所周知,html頁面中所有帶有src屬性的標籤(<img>,<script>iframe)都擁有跨域能力。所以最簡單的實現方式就是動態載入JS。

客戶端

function todo(data){
  console.log('The author is: '+ data.name);
}

var script = document.createElement('script');
/* callback引數,用來指定回撥函式的名字。 */
script.src = 'http://www.yeaseonzhang.com/author?callback=todo';
document.body.appendChild(script);複製程式碼

服務端

/* 伺服器收到這個請求以後,會將資料放在回撥函式的引數位置返回。 */
todo({"name": "yeaseonzhang"});複製程式碼

todo()函式會被作為全域性函式來執行,只要定義了todo()函式,該函式就會被立即呼叫。

postMessage

window.postMessage是HTML5中一個安全的,基於事件的訊息API。

otherWindow.postMessage(message, targetOrigin, [transfer]);複製程式碼

postMessage(),方法包含三個引數:

  • message: 訊息內容
  • targetOrigin: 接受訊息視窗的源,即"協議 + 域名 + 埠"。也可以設定為萬用字元*,向所有視窗傳送
  • transfer: 可選引數(布林值),是一串和message 同時傳遞的Transferable物件. 這些物件的所有權將被轉移給訊息的接收方,而傳送一方將不再保有所有權。

傳送者接收者都可以通過message事件,監聽對方的訊息。message事件的事件物件event包含三個屬性:

  • event.source: 傳送訊息的視窗物件的引用,可以用此在兩個視窗建立雙向通訊。
  • event.origin: 傳送訊息的URI
  • event.data: 訊息內容

傳送者: domain-a.com/a.html

<script>
  var newWindow = window.open('http://domain-b.com/b.html');
  /* 向b.html傳送訊息 */
  newWindow.postMessage('Hello', 'http://domain-b.com/b.html');

  /* 雙向通訊,接收b.html的回覆訊息 */
  var onmessage = function (event) {
    var data = event.data;
    var origin = event.origin;
    var source = event.source;
    if (origin == 'http://domain-b.com/b.html') {
      console.log(data); //Nice to see you!
    }
  };
  window.addEventListener('message', onmessage, false);
</scirpt>複製程式碼

接收者:domain-b.com/b.html

<script>
  var onmessage = function (event) {
    var data = event.data;
    var origin = event.origin;
    var source = event.source;
    if (origin == 'http://domain-a.com/a.html') {
      console.log(data); //Hello
      /* 回覆a.html的訊息 */
      source.postMessage('Nice to see you!', 'http://domain-a.com/a.html');
    }
  };
  window.addEventListener('message', onmessage, false);
</script>複製程式碼

WebSocket

WebSocket是一種HTML5的一種新的協議,它實現了瀏覽器與伺服器的全雙工通訊,同時也是跨域的一種解決方案,詳細介紹請訪問MDN

/* websocket協議為ws/wss, 類似http/https的區別 */
wsUrl = 'wss://127.0.0.1:8090/ws/';

/* 傳送 */
ws = new WebSocket(wsUrl);

/* 連線成功建立時呼叫 */
ws.onopen = function (event) {
  console.log("websocket command onopen");
  var msg = {
    username: 'YeaseonZhang'
  }
  /* 通過 send() 方法向服務端傳送訊息,引數必須為字串 */
  ws.send(JSON.stringify(msg));
};

/* 服務端向客戶端傳送訊息時呼叫 */
ws.onmessage = function (event) {
  /* event.data包含了服務端傳送過來的訊息 */
  console.log("websocket command onmessage: " + event.data);
  if (event.data === 'success') {
    /* 通過 close() 方法斷開websocket連線 */
    ws.close();
  }
};

/* 連線被關閉時呼叫 */
ws.onclose = function (event) {
  console.log("websocket command onclose: " + event.data);
};

/* 出現錯誤時呼叫 */
ws.onerror = function (event) {
  console.log("websocket command onerror: " + event.data);
};複製程式碼

WebSocket的優勢是除了可以實現跨域,還有就是可以保持長連線,而不需要通過輪詢實現實時性。

CORS

CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。

只需要後端同學支援就ok,前端不需要做很多額外工作(除了攜帶cookie)。

只要伺服器返回的相應中包含頭部資訊Access-Control-Allow-Origin: domain-namedomain-name為允許跨域的域名,也可以設定成*,瀏覽器就會允許本次跨域請求。

結語

以上就是我所瞭解的跨域的解決方案,希望對你有所幫助。

相關文章