1. 程式人生 > >跨域問題的5種解決方案

跨域問題的5種解決方案

跨域


什麼是跨域

跨域是由瀏覽器的同源策源產生的,是指頁面請求的介面地址,必須與頁面的url地址處於同域上(即域名、埠、協議相同)。這是為了防止某域名下面的介面,被其他域名下的網頁非法呼叫,是瀏覽器對JavaScript施加的安全限制。

跨域產生的原因

在我們日常的開發中,靜態資源是放在本地電腦上面的,訪問這些資源通常通過IP地址(127.0.0.1)或者localhost來訪問的,與線上伺服器所在的域名不符,不能順利的進行介面的呼叫

跨域的解決辦法

使用JSONP

http://blog.csdn.net/u014607184/article/details/52027879


設定代理解決跨域
正向代理

代理,也稱正向代理,是指一個位於客戶端和目標伺服器(target server)之間的伺服器,為了從目標伺服器取得內容,客戶端向代理髮送一個請求並指定目標(目標伺服器),然後代理向目標伺服器轉交請求並將獲得的內容返回給客戶端。

舉個小栗子:
「客戶端」可以看作一個黑社會大佬,「目標伺服器」可以看作一家飯店,「代理伺服器」可以看作小弟。

  • 「老大」想吃飯店的醬排骨飯,就讓「小弟」去買,「小弟」跑到「飯店」要個醬排骨飯。

  • 「飯店」醬排骨飯做好,送到「小弟」手上,「小弟」最後再把醬排骨飯拿給「大佬」。

說白了,小弟就是個跑腿的,代理大佬的需求。

資料流程:

  • 資料請求過程:瀏覽器-》代理伺服器-》目標伺服器
  • 資料返回過程:目標伺服器-》代理伺服器-》瀏覽器

應用:
最經典的應用就是科學上網:我是一個國內使用者,我訪問不了google,但是我能訪問一個香港的某個代理伺服器。
這個香港的代理伺服器可以訪問google,於是我先把請求傳送到那個代理伺服器,告訴他我需要訪問google,代理伺服器去取內容,最後返回給我。

就好比,大佬被抓起來坐牢了,不能出去買醬排骨,只好拜託小弟去買回來。

反向代理

反向代理(Reverse Proxy)是指以代理伺服器來接受internet上的連線請求

,然後將請求轉發給內部網路上的伺服器,並將從伺服器上得到的結果返回給internet上請求連線的客戶端,此時代理伺服器對外就表現為一個反向代理伺服器。

資料流程:
- 資料請求過程:瀏覽器 =>【反向代理伺服器=>處理資料的伺服器】

  • 資料返回過程:【處理資料的伺服器=>反向代理伺服器】=>瀏覽器

通俗地說:
「瀏覽器」可以看作食客,「【反向代理伺服器-》處理資料的伺服器】」這一個整體可以看作飯店,其中「反向代理服務」相當於點單的服務員。「處理資料的伺服器」可以理解為是廚師。

「食客」向來到「飯店」向「服務員」點菜,但服務員並不會真正去做菜,他是下達命令讓「廚師」去做菜。

「廚師」把菜做好了給「服務員」,「服務員」再把菜端給「食客」。

在外部看來,「代理伺服器」和「處理資料的伺服器」是一個整體。就好比,食客只會去飯店吃飯,而不是去找廚師吃飯(即對於瀏覽器來說,到達反向代理伺服器已經完成任務了,後面的操作由反向代理伺服器負責)。
具體飯店怎麼操作,對食客是透明的。有可能某個服務員即當伺服器也當廚師(即反向代理伺服器和處理資料的伺服器是同一臺PC機)。

補充一下,沒有反向代理,就好比沒有了服務員,食客直接向廚師要吃的。譬如,你餓了,直接叫媽媽做飯是一樣的(少了下訂單的步驟)

比較

從用途上來講:

  • 正向代理的典型用途是為在防火牆內的區域網客戶端提供訪問Internet的途徑。正向代理還可以使用緩衝特性減少網路使用率。

  • 反向代理的典型用途是為後端的多臺伺服器提供負載平衡,或為後端較慢的伺服器提供緩衝服務。

從安全性來講:

  • 正向代理允許客戶端通過它訪問任意網站並且隱藏客戶端自身,因此你必須採取安全措施以確保僅為經過授權的客戶端提供服務。

  • 反向代理對外都是透明的,訪問者並不知道自己訪問的是一個代理。

從使用方來看:

  • 正向代理是瀏覽器端進行配置的,與伺服器端無關,甚至可以對服務端隱藏。
  • 反向代理是伺服器端配置的,對瀏覽器端是透明的。

利用正向代理實現跨域
實現原理

對正向代理伺服器進行配置,當獲取非介面資料時,讓代理伺服器指向開發者本機的資源。當訪問介面時,訪問後端介面資料

相當於大佬讓小弟把醬排骨飯裡面的飯和醬排骨分開買,飯自己家煮,醬排骨才去飯店買。

程式執行過程

  1. 瀏覽器訪問頁面,假設訪問淘寶頁面:taobao.com/index.html(假設這個頁面中呼叫了taobao.com/api/getNew獲取最新商品的介面)

  2. taobao.com/index.html請求經過代理伺服器,根據配置,index.html頁面請求127.0.0.1:3000

  3. 127.0.0.1:3000返回index.html檔案給瀏覽器。

  4. 瀏覽器執行index.html頁面,發起taobao.com/api/getNew請求。

  5. taobao.com/api/getNew請求經過代理伺服器,但由於沒有對這個介面進行特殊配置,這個介面會正常訪問道淘寶伺服器。

  6. 淘寶伺服器接受到taobao.com/api/getNew請求,檢查請求頭的hosts欄位,發現是taobao.com,沒有跨域,將結果返回給代理伺服器。

  7. 代理伺服器拿到結果,返回給瀏覽器,瀏覽器進行解析顯示。

  8. 代理配置(以mac下的charles為例)

利用反向代理實現跨域
反向代理需要用到nginx,再此不做過多介紹(自己動手,豐衣足食o!)
實現原理

原理大體相同,但是處理的端不同,反向代理是在伺服器端進行處理。首先修改hosts檔案,將域名指向開發者的電腦本身,把自己偽裝成服務端,再通過nginx對不同的請求進行轉發,把靜態資源指向開發者本地電腦的資源,將介面指向實際的伺服器。

相當於把飯店設定在了黑社會的樓下,去樓下買醬排骨飯的時候,飯店米飯自己做,醬排骨則偷偷跑去別的飯店買。

代理配置
1. 設定hosts檔案,將目標域名指向本機。

  1. 編輯nginx配置,對不同的資源請求,指向到對應地址。同樣的,將靜態資源指向本機服務,將介面指向真正的伺服器。

  2. 程式執行過程
    瀏覽器訪問頁面,假設訪問淘寶頁面:taobao.com/index.html

  3. taobao.com域名解析先經過hosts檔案配置,發現taobao.com域名指向127.0.0.1,則向本機發起請求。

  4. nginx接收到taobao.com/index.html請求,根據nginx的配置,將把這個請求轉發給127.0.0.1:3000。

  5. 瀏覽器執行index.html檔案,發起taobao.com/api/getNew請求

  6. nginx接收到taobao.com/api/getNew請求請求,根據nginx的配置,將把這個請求轉發給真正的淘寶伺服器中。

  7. 淘寶伺服器將資料返回給nginx,再返回給瀏覽器執行。

簡單的對比

  • 使用charles等正向代理方式比較簡單,需要掌握的知識點也比較少。但相應的其可配置性較弱,僅適合中小型專案使用。

  • 使用nginx的反向代理則相對複雜一些,需要了解基本的nginx配置。但其可配置性較強,支援URL的正則匹配,設定優先順序等,適合複雜的專案使用。

使用跨域資源共享(CORS)

CORS(Cross-Origin Resource Sharing)跨域資源共享,定義了必須在訪問跨域資源時,瀏覽器與伺服器應該如何溝通。CORS背後的基本思想就是使用自定義的HTTP頭部讓瀏覽器與伺服器進行溝通,從而決定請求或響應是應該成功還是失敗。
使用方法也很簡單,在php後端設定 Access-Control-Allow-Origin 頭即可

嵌入iframe

不同的框架之間是可以獲取window物件的,但卻無法獲取相應的屬性和方法。
比如,有一個頁面,它的地址是http://www.example.com/a.html
在這個頁面裡面有一個iframe,它的src是http://example.com/b.html,
很顯然,這個頁面與它裡面的iframe框架是不同域的,所以我們是無法通過在頁面中書寫js程式碼來獲取iframe中的東西的
這個時候,document.domain就可以派上用場了,
我們只要把http://www.example.com/a.htmlhttp://example.com/b.html這兩個頁面的document.domain都設成相同的域名就可以了。
但要注意的是,document.domain的設定是有限制的,我們只能把document.domain設定成自身或更高一級的父域,且主域必須相同。

使用window.postMessage方法

這個東西是HTML5引入的,可以在不同的window下傳遞資料,不受域的影響。目前IE8+、FireFox、Chrome、Opera等瀏覽器都已經支援該方法
window.postMessage(message,targetOrigin)
呼叫postMessage方法的window物件是指要接收訊息的那一個window物件,該方法的第一個引數message為要傳送的訊息,型別只能為字串;
第二個引數targetOrigin用來限定接收訊息的那個window物件所在的域,如果不想限定域,可以使用萬用字元 * 。
需要接收訊息的window物件,可是通過監聽自身的message事件來獲取傳過來的訊息,訊息內容儲存在該事件物件的data屬性中。

/*
 * A視窗的域名是<http://example.com:8080>,以下是A視窗的script標籤下的程式碼:
 */

var popup = window.open(...popup details...);

// 如果彈出框沒有被阻止且載入完成

// 這行語句沒有傳送資訊出去,即使假設當前頁面沒有改變location(因為targetOrigin設定不對)
popup.postMessage("The user is 'bob' and the password is 'secret'",
                  "https://secure.example.net");

// 假設當前頁面沒有改變location,這條語句會成功新增message到傳送佇列中去(targetOrigin設定對了)
popup.postMessage("hello there!", "http://example.org");

function receiveMessage(event)
{
  // 我們能相信資訊的傳送者嗎?  (也許這個傳送者和我們最初開啟的不是同一個頁面).
  if (event.origin !== "http://example.org")
    return;

  // event.source 是我們通過window.open開啟的彈出頁面 popup
  // event.data 是 popup傳送給當前頁面的訊息 "hi there yourself!  the secret response is: rheeeeet!"
}
window.addEventListener("message", receiveMessage, false);
/*
 * 彈出頁 popup 域名是<http://example.org>,以下是script標籤中的程式碼:
 */

//當A頁面postMessage被呼叫後,這個function被addEventListenner呼叫
function receiveMessage(event)
{
  // 我們能信任資訊來源嗎?
  if (event.origin !== "http://example.com:8080")
    return;

  // event.source 就當前彈出頁的來源頁面
  // event.data 是 "hello there!"

  // 假設你已經驗證了所受到資訊的origin (任何時候你都應該這樣做), 一個很方便的方式就是把enent.source
  // 作為回信的物件,並且把event.origin作為targetOrigin
  event.source.postMessage("hi there yourself!  the secret response " +
                           "is: rheeeeet!",
                           event.origin);
}

window.addEventListener("message", receiveMessage, false);

例子來自:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage