JavaScript 複習之 同源限制

DreamTruth發表於2019-03-05

所謂“同源”指的是“三個相同”:協議相同、域名相同及埠相同。

非同源,共有三種行為受到限制。

  1. 無法讀取非同源網頁的 Cookie、LocalStorage 和 IndexedDB。

  2. 無法接觸非同源網頁的 DOM。

  3. 無法向非同源地址傳送 AJAX 請求(可以傳送,但瀏覽器會拒絕接受響應)。

window.postMessage()

這個 API 為window物件新增一個window.postMessage方法,允許跨視窗通訊,不論這倆個視窗是否同源。

// 父視窗開啟一個子視窗
var popup = window.open('http://bbb.com', 'title');
// 父視窗向子視窗發訊息
popup.postMessage('Hello World!', 'http://bbb.com');
複製程式碼

方法接受兩個引數,第一個引數是具體的資訊內容,第二個引數是接收訊息的視窗的源,即“協議 + 域名 + 埠”。也可以設為*,表示不限制域名,向所有視窗傳送。

子視窗向父視窗傳送訊息的寫法類似。

// 子視窗向父視窗發訊息
window.opener.postMessage('Nice to see you', 'http://aaa.com');
複製程式碼

父子視窗通過message事件,監聽對方的訊息。

// 父視窗和子視窗都可以用下面的程式碼,
// 監聽 message 訊息
window.addEventListener('message', function (e) {
  console.log(e.data);
},false);
複製程式碼

message事件的引數是事件物件event,提供三個屬性

  1. event.source:傳送訊息的視窗
  2. event.origin:訊息發向的網址
  3. event.data:訊息內容

下面的例子是,子視窗通過event.source屬性引用父視窗,然後傳送訊息。

window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
  event.source.postMessage('Nice to see you!', '*');
}
複製程式碼

上面程式碼有幾個地方需要注意。首先,receiveMessage函式裡面沒有過濾資訊的來源,任意網址發來的資訊都會被處理。其次,postMessage方法中指定的目標視窗的網址是一個星號,表示該資訊可以向任意網址傳送。通常來說,這兩種做法是不推薦的,因為不夠安全,可能會被惡意利用。

event.origin屬性可以過濾不是發給本視窗的訊息。

window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
  if (event.origin !== 'http://aaa.com') return;
  if (event.data === 'Hello World') {
    event.source.postMessage('Hello', event.origin);
  } else {
    console.log(event.data);
  }
}
複製程式碼

AJAX

同源政策規定,AJAX 請求只能發給同源的網址,否則就報錯。

除了架設伺服器代理(瀏覽器請求同源伺服器,再由後者請求外部服務),有三種方法規避這個限制。

  • JSONP

  • WebSocket

  • CORS

jsonp

是伺服器和客戶端跨源通訊的常用方法,特點就是簡單適用。

基本思想是,網頁通過新增一個<scritp>元素,向伺服器請求 JSON 資料,這種做法不受同源策略限制;伺服器收到請求後,將資料放在一個指定名字的回撥函式裡傳回來。

首先,網頁動態插入<scritp>元素,由他向跨源網址發出請求。

function addScriptTag(src){
    let 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"
});
複製程式碼

WebSocket

是一種通訊協議,使用ws://(非加密)和wss://(加密)作為協議字首。該協議不實行同源策略,只要伺服器支援,就可以通過它進行跨源通訊。

下面是一個瀏覽器 發出的 WebStock 請求的頭部資訊

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,表示請求的請求源,即發自哪個域名。

正因為有了這個欄位,所以 WebStock 才沒有實行同源策略。因為伺服器可以根據這個欄位,判斷是否許可本次通訊。如果該域名在白名單內,伺服器就可以做出如下回應

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
複製程式碼

CORS

是跨源資源分享(Cross-Origin Resource Sharing)的縮寫。屬於跨源 AJAX 請求的根本解決方法。允許任何型別的請求。

CORS 需要瀏覽器和伺服器同時支援,目前所有瀏覽器都支援該功能。

實現 CORS 通訊的關鍵是伺服器,只要伺服器實現了 CORS 介面,就可以跨域通訊。

相關文章