1. 程式人生 > >前端跨域的理解和解決方案

前端跨域的理解和解決方案

何為跨域?

首先,我們得先理解一下何為跨域?所謂跨域,即網站的協議名 protocol(例如 http ://) 、域名 host (例如:www.example.com)、埠號 port (例如 80 ,預設埠可以省略) 這三個中的任意一個不同,網站之間的資料傳輸或者請求就屬於跨域請求了。

這是由於瀏覽器的同源策略,為了防範跨站指令碼的攻擊,禁止客戶端指令碼對不同域的服務進行跨站呼叫,但是跨域並非瀏覽器限制了發起跨站請求,而是跨站請求可以正常發起,但返回結果被瀏覽器攔截了有些瀏覽器不允許從HTTPS協議的域 跨域訪問 HTTP協議,比如Chrome和Firefox,這些瀏覽器在請求還未發出的時候就會攔截請求,這是一個特例。

如果是非同源,共有三種行為受到限制:(1)cookie、LocalStorage 和 IndexDB 無法讀取;(2)DOM 無法獲得;(3)AJAX請求不能傳送。

【這裡再解釋一下同源(具體定義可以檢視 MDN),如果兩個頁面的協議,埠(如果有指定)和域名都相同,則兩個頁面具有相同的源。IE 有例外,一是授信範圍:兩個相互之間高度互信的域名,不遵守同源策略的限制;二是埠:IE 未將埠號加入到同源策略的組成成分中。更多有關源的介紹可以檢視 MDN :https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy

 如何去解決?

 接著,既然我們知道了何為跨域,跨越有時候在實際的開發中有時候又無法避免,下面介紹幾種常見的跨域方法:

1、利用 JSONP 實現跨域

JSONP(JSON with Padding)是 JSON 的一種“使用模式”。利用 JSON 實現跨域的原理是:HTML 的 script 標籤是不受同源策略的限制的,可以通過 script 標籤載入並執行其他的域的 js 檔案的。

例如通過 jQery 封裝的方法可以很方便地進行 JSONP 的操作:

$.ajax({
  type: 'GET',
  url: 'http://jjjjjjjj.com/data',
  // 需要提交給服務端的資料:
  data: { name: '燕子' },
  // 指定資料型別:
  dataType: 'jsonp',
  timeout: 300,
  success: function(data){
    this.append(data.project.html)
  },
  error: function(xhr, type){
    alert('資料獲取失敗!')
  }
})
//使用$.getJSON
$.getJSON('http://jjjjjjjj.com/data?callback=?,function(data)'){
    //處理獲得的json資料
});

總結:

  • JSONP 的相容性好,在更古老的瀏覽器都可以執行,不需要 XMLHTTPRequest 或 ActiveX 的支援,並且在請求完畢後可以通過呼叫 callback 的方式回傳結果;
  • 然而缺點是,它只支援 GET 請求 而不支援 POST 等其他型別的 HTTP 請求;還有 只支援跨域 HTTP 請求這種情況,不能解決不同域的兩個頁面的之間 JavaScript 呼叫的問題; 

2、利用 CORS 實現跨域

CORS (Cross-Origin Resource Sharing)跨域資源共享,定義了必須在訪問跨域資源時,瀏覽器與伺服器應該如何溝通。CORS 背後的思想是使用自定義的 HTTP 頭部,讓伺服器能宣告哪些來源可以通過瀏覽器訪問該伺服器上的資源,從而決定請求或響應是應該成功還是失敗,CORS 本身並非絕對的安全,可利用 OAuth2 加強保障。(更多關於 CORS 的詳解可以檢視 阮一峰老師的一篇文章:跨域資源共享 CORS 詳解

header("Access-Control-Allow-Origin: *")       //“*”號表示允許任何域向我們的服務端提交請求
header("Access-Control-Allow-Origin: http://jjjjj.jd.com")      //也可以設定指定的域名
與 JSONP 相比,CORS 更為先進、方便:
  • CORS 支援所有型別的 HTTP 請求;
  • 使用CORS,開發者可以使用普通的XMLHttpRequest發起請求和獲得資料,比起JSONP有更好的錯誤處理;
  • 絕大多數現代瀏覽器都已經支援了CORS;

3、window.name

當window 的 loaction 變化時,頁面重新載入,它的 name 屬性可以依然保持不變。每個頁面的對 window.name 都有讀寫許可權,window.name 是持久存在一個視窗載入過所有頁面中的。 我們可以在頁面 A中用iframe載入其他域的頁面B,而頁面B中用JavaScript把需要傳遞的資料賦值給window.name,iframe載入完成之後(iframe.onload),頁面A修改iframe的地址,將其變成同域的一個地址,然後就可以讀出iframe的window.name的值了(因為A中的window.name和iframe中的window.name互相獨立的,所以不能直接在A中獲取window.name,而要通過iframe獲取其window.name)。這個方式非常適合單向的資料請求,而且協議簡單、安全。不會像JSONP那樣不做限制地執行外部指令碼。

4、document.domain 跨域(只適用於不同子域的框架間的互動)

同源策略不能用 ajax 方法去請求不同源的文件,還有一個限制就是瀏覽器不同域的框架之間不能進行 JS 的互動操作的,不同的框架可以獲取 window 物件,但無法獲取相應的屬性和方法。

這個時候,我們可以通過把兩個頁面的 document.domain 都設成相同的域名就可以,不過 window.domain 的設定也是有限制的,只能把 window.domain 設定成自身或更高一級的父域,且主域必須相同

5、HTML5 的 postMessage 方法

高階瀏覽器Internet Explorer 8+, chrome,Firefox , Opera 和 Safari 都將支援這個功能。這個功能主要包括接受資訊的"message"事件和傳送訊息的"postMessage"方法。

window.postMessage() 方法被呼叫時,會在所有頁面指令碼執行完畢之後(例如:在該方法之後設定的事件、之前設定的timeout 事件)向目標視窗派發一個  MessageEvent 訊息。 該 MessageEvent 訊息有四個屬性需要注意: message 屬性表示該 message 的型別; data 屬性為 window.postMessage 的第一個引數;origin 屬性表示呼叫 window.postMessage()  方法時呼叫頁面的當前狀態; source 屬性記錄呼叫 window.postMessage() 方法的視窗資訊。