跨域問題的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的途徑。正向代理還可以使用緩衝特性減少網路使用率。
反向代理的典型用途是為後端的多臺伺服器提供負載平衡,或為後端較慢的伺服器提供緩衝服務。
從安全性來講:
正向代理允許客戶端通過它訪問任意網站並且隱藏客戶端自身,因此你必須採取安全措施以確保僅為經過授權的客戶端提供服務。
反向代理對外都是透明的,訪問者並不知道自己訪問的是一個代理。
從使用方來看:
- 正向代理是瀏覽器端進行配置的,與伺服器端無關,甚至可以對服務端隱藏。
- 反向代理是伺服器端配置的,對瀏覽器端是透明的。
利用正向代理實現跨域
實現原理
對正向代理伺服器進行配置,當獲取非介面資料時,讓代理伺服器指向開發者本機的資源。當訪問介面時,訪問後端介面資料
。
相當於大佬讓小弟把醬排骨飯裡面的飯和醬排骨分開買,飯自己家煮,醬排骨才去飯店買。
程式執行過程
瀏覽器訪問頁面,假設訪問淘寶頁面:taobao.com/index.html(假設這個頁面中呼叫了taobao.com/api/getNew獲取最新商品的介面)
taobao.com/index.html請求經過代理伺服器,根據配置,index.html頁面請求127.0.0.1:3000
127.0.0.1:3000返回index.html檔案給瀏覽器。
瀏覽器執行index.html頁面,發起taobao.com/api/getNew請求。
taobao.com/api/getNew請求經過代理伺服器,但由於沒有對這個介面進行特殊配置,這個介面會正常訪問道淘寶伺服器。
淘寶伺服器接受到taobao.com/api/getNew請求,檢查請求頭的hosts欄位,發現是taobao.com,沒有跨域,將結果返回給代理伺服器。
代理伺服器拿到結果,返回給瀏覽器,瀏覽器進行解析顯示。
代理配置(以mac下的charles為例)
利用反向代理實現跨域
反向代理需要用到nginx,再此不做過多介紹(自己動手,豐衣足食o!)
實現原理
原理大體相同,但是處理的端不同,反向代理是在伺服器端進行處理。首先修改hosts檔案,將域名指向開發者的電腦本身,把自己偽裝成服務端,再通過nginx對不同的請求進行轉發,把靜態資源指向開發者本地電腦的資源,將介面指向實際的伺服器。
相當於把飯店設定在了黑社會的樓下,去樓下買醬排骨飯的時候,飯店米飯自己做,醬排骨則偷偷跑去別的飯店買。
代理配置
1. 設定hosts檔案,將目標域名指向本機。
編輯nginx配置,對不同的資源請求,指向到對應地址。同樣的,將靜態資源指向本機服務,將介面指向真正的伺服器。
程式執行過程
瀏覽器訪問頁面,假設訪問淘寶頁面:taobao.com/index.htmltaobao.com域名解析先經過hosts檔案配置,發現taobao.com域名指向127.0.0.1,則向本機發起請求。
nginx接收到taobao.com/index.html請求,根據nginx的配置,將把這個請求轉發給127.0.0.1:3000。
瀏覽器執行index.html檔案,發起taobao.com/api/getNew請求
nginx接收到taobao.com/api/getNew請求請求,根據nginx的配置,將把這個請求轉發給真正的淘寶伺服器中。
淘寶伺服器將資料返回給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.html 和http://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