前端跨域之原因&&方案&&原理

原创 Lin_Grady 教程 javascript/jQuery 302阅读 2017-12-24 20:10:16 举报

這是一道前端跨不過躲不掉面試必備的知識,掙扎多年沒能做到刻骨銘心深入脊髓,只能好好寫篇博文記錄起來了;

什麼是跨域?

廣義來說,A域執行的文檔腳本試圖去請求B域下的資源是不被允許的,源於瀏覽器的同源策略,所謂同源指的是協議,域名 (domain) ,端口都相同,例如下面四個URL就屬於不同源;
[quote]舉例URL: http://www.a.com(:80)/index.html
協議不同: http://www.a.com(:80)/index.html
域名不同: www.b.com(:80)/index.html" target="_blank">http://www.b.com(:80)/index.html
端口不同: (:8080)/index.html[/quote" target="_blank">http://www.a.com(:8080)/index.html[/quote]

為什麼會有同源策略(Same origin policy)?

目的就是為了保證用戶信息的安全,阻止和減少通過惡意分子竊取數據(例如普遍的CSRF攻擊)。
首先同源策略也分兩種XmlHttpRequest同源策略DOM同源策略
我們常說的同源策略一般是指XmlHttpRequest同源策略;
例如用戶登陸某個購物平台;
[quote]1,用戶登陸www.shopping.com購物頁,用戶信息被保存在cookie中;
2,用戶被引導進惡意詐騙頁面www.spite.com,執行了當中的惡意代碼請求www.shopping.com,默認會發送對應cookie信息;
3,www.shopping.com驗證通過,返回請求數據,這時候相當於賬戶信息和權限都在他人手中了
這一過程不為用戶所知道,這是屬於XmlHttpRequest同源策略,總的來說就是禁止使用XHR對象向不同源的服務器地址發起HTTP請求。[/quote]

至於DOM同源策略也是差不多道理
[quote]1,用戶被引導進惡意詐騙頁面www.spite.com,它用iframe偽裝成www.shopping.com購物頁;
2,用戶登陸賬號密碼,惡意詐騙頁面就能拿用戶信息跨域訪問www.shopping.com的DOM節點;
這一過程不為用戶所知道,這是屬於DOM同源策略,總的來說就是禁止對不同源頁面DOM進行操作。[/quote]

於是乎同源策略應運而生,主要限制在於
[quote]1,Cookie、LocalStorage 和 IndexDB 無法讀取。
2,DOM 無法獲得。
3,AJAX 請求不能發送。[/quote]
需要注意的是同源策略只對網頁的HTML文檔做了限制,對加載的其他靜態資源如javascript、css、圖片等仍然認為屬於同源。

同源策略是絕對的么?

既然同源策略存在的重要性不言而喻,為什麼我們需要嘗試繞過這道坎?
例如銀行的安全性夠高了吧,相對應的限制性也是極高的,同理可得安全往往也是犧牲了部分靈活自由的。今時今日的互聯網人流量,如果大型網站全部文件都只能訪問指定一台服務器,根本不可能,也絕對支撐不起雙十一的電商網站用戶流量;於是在遵循同源策略的基礎下,瀏覽器也適當地給開發者留下一些“密道”。

怎麼繞過同源策略?

首先,一般來說協議和端口造成的跨域問題大部分方法是沒有辦法繞過的。

然後我們認識一下強大的跨域神器

IFrame 對象

IFrame 對象代表一個 HTML 的內聯框架,由於它是獨立的頁面,因而擁有自己的事件,擁有自己的窗口對象(contentWindow);

前端跨域之原因&&方案&&原理
很多時候我們想要繞過同源策略都得經過iframe對象其他窗口對象

一,document.domain

這個方法實現前提:這兩個域名必須屬於同一個基礎域名,即主域相同,子域可以不同;

原理:document.domain 默認的值是整個域名,所以即使兩個域名的二級域名一樣,那麼他們的 document.domain 也不一樣。通過把兩個頁面的document.domain都設置成相同域名(只能設置成自身或者更高一級的父域,且主域必須相同),兩個頁面之間可以獲取window對象,和下圖部分屬性和方法;

前端跨域之原因&&方案&&原理

二級域名SLD(Second-level domain):是互聯網DNS等級之中,處於頂級域名之下的域。二級域名是域名的倒數第二個部份,二級域名就是主域名分出來的域名。
1)二級域名是寄存在主域名之下的域名。
2)二級域名屬於一個獨立的分支,他有自己的收錄、快照、PR值、反鏈等。
3)當主域名受到懲罰,二級域名也會連帶懲罰。

此為例,我們有域名劃分為:
[quote].com 頂級域名
example.com 一級域名
A.example.com 二級域名
B.example.com 二級域名[/quote]

//example.com
html 代碼

//A.example.com
html 代碼

一般使用不建議直接頁面插入iframe,所以我們可以自己封裝方法使用
javascript 代碼

也可以在服務器通過設置Set-Cookie,讓客戶端下擁有相同父域下所有子域名共享cookie;

二,window.name

原理:在一個窗口 (window) 的生命周期內,窗口載入的所有的頁面都是共享一個 window.name 的,每個頁面對 window.name 都有讀寫的權限,window.name 是持久存在一個窗口載入過的所有頁面中的,並不會因新頁面的載入而進行重置。換成人話就是在同一個窗口打開的所有頁面即使不同域名不同頁面它們也會共享同一個name值(包括跳轉,iframe等都屬於並且可以支持非常長的 name 值2MB視瀏覽器而定)

優點:與 document.domain 方法相比,放寬了域名後綴要相同的限制,可以從任意頁面獲取 string 類型的數據。

例如下面會跳到百度,然後進入打印window.name依然能拿到設置的字符串
javascript 代碼

上面的例子應該能起到一個拋磚引玉的作用了,接下來說所怎麼通過window.name進行跨域;
條件,分別需要三個頁面http://www.A.com/index.html,http://www.B.com/data.php,http://www.A.com/data.html,注意是否同源
1,index.html通過動態創建一個iframe作為中間的橋樑,然後src屬性指向遠端服務器data.php發起請求;
2,data.php設置name,因為不能跨域操作name值,所以只能再次重定向回同源頁面data.html;
3,同源頁面data.html可以直接獲取數據,index.html也可以無障礙操作iframe,拿到name值後立馬移除iframe保證安全;

//http://www.A.com/index.html
javascript 代碼

//http://www.B.com/data.html
javascript 代碼

三,location.hash

這個方法實現前提有兩個:
1,利用url改變hash(URL 的錨部分,從 # 號開始的部分)並不會導致頁面刷新;
2,HTML 5新增的事件onhashchange,當#值發生變化時,就會觸發這個事件;

優點:
1,HTTP請求過程中不會攜帶hash,所以這部分的修改不會產生HTTP請求,只有轉碼瀏覽器才會將其作為實義字符處理;
2,#後面出現的任何字符,都會被瀏覽器解讀為位置標識符,瀏覽器只會滾動到相應位置,不會重新加載網頁。
缺點:
1,每次修改都會產生瀏覽器歷史記錄;
2,有些瀏覽器不支持onhashchange事件,需要輪詢來獲知URL的改變;
3,數據直接暴露在了url中
4,過程繁瑣,起碼我認為是

思路:
條件,分別需要三個頁面http://www.A.com/A.html,http://www.B.com/B.html,http://www.A.com/C.html,注意是否同源
1,A.html下創建iframe打開B.html,聲明回調方法備用,並且修改hash;
2,B.html下創建iframe打開C.html, 監聽onhashchange事件改變hash;
3,C.html下監聽onhashchange事件,因為跟A.html同源可以往上層追蹤觸發到A.html的回調方法

//http://www.A.com/A.html
javascript 代碼

//http://www.B.com/B.html
javascript 代碼

//http://www.A.com/C.html
javascript 代碼

四,window.postMessage

postMessage是HTML5 XMLHttpRequest Level 2中的API,可以安全地實現跨源通信。

優點:
1,基本支持主流瀏覽器和IE8+,支持任意基本類型或可複製的對象;
2,真正的跨域方法,包括協議和端口不同都可以實現;
缺點:
一般來說參數 message被使用結構化克隆算法進行序列化。這意味着您可以將各種各樣的數據對象安全地傳遞到目標窗口,而不必自己序列化它們,但部分瀏覽器只支持字符串,所以傳參非字符串最好用JSON.stringify()序列化。

前端跨域之原因&&方案&&原理

先看看用法
javascript 代碼

[quote]otherWindow:其他窗口的一個引用,比如iframe的contentWindow屬性、執行window.open返回的窗口對象、或者是命名過或數值索引的window.frames。
message:將要發送到其他 window的數據。它將會被結構化克隆算法序列化。這意味着你可以不受什麼限制的將數據對象安全的傳送給目標窗口而無需自己序列化。
targetOrigin:通過窗口的origin屬性來指定哪些窗口能接收到消息事件,其值可以是字符串""(表示無限制)或者一個URI。在發送消息的時候,如果目標窗口的協議、主機地址或端口這三者的任意一項不匹配targetOrigin提供的值,那麼消息就不會被發送;只有三者完全匹配,消息才會被發送。這個機制用來控制消息可以發送到哪些窗口;例如,當用postMessage傳送密碼時,這個參數就顯得尤為重要,必須保證它的值與這條包含密碼的信息的預期接受者的orign屬性完全一致,來防止密碼被惡意的第三方截獲。如果你明確的知道消息應該發送到哪個窗口,那麼請始終提供一個有確切值的targetOrigin,而不是。不提供確切的目標將導致數據泄露到任何對數據感興趣的惡意站點。
transfer(可選):是一串和message 同時傳遞的 Transferable 對象. 這些對象的所有權將被轉移給消息的接收方,而發送一方將不再保有所有權。[/quote]

//http://www.A.com/index.html
javascript 代碼

//http://www.B.com/data.html
javascript 代碼

在這裡出於本地調試原因,所以沒有驗證過信息,實際使用中務必使用origin和source屬性驗證發件人的身份,targetOrigin設置可信任的網站。
有興趣的可以在本地打開兩個頁面通信,targetOrigin設為*就可以了,然後得到下圖的對象

前端跨域之原因&&方案&&原理

五,JSONP (JSON with padding)

這個方法實現前提有兩個:
1,還記得上面提過的同源策略只對網頁的HTML文檔做了限制,對加載的其他靜態資源如javascript、css、圖片等仍然認為屬於同源。
2,JSON的誕生:
[quote]指的是 JavaScript 對象表示法(JavaScript Object Notation)
是輕量級的文本數據交換格式
獨立於語言:JSON 使用 Javascript語法來描述數據對象,但是 JSON 仍然獨立於語言和平台。JSON 解析器和 JSON 庫支持許多不同的編程語言。 目前非常多的動態(PHP,JSP,.NET)編程語言都支持JSON。
具有自我描述性,更易理解[/quote]

優點:
1,因為script標籤引入的文件內容是不能夠被客戶端的js獲取到的(實際上凡是擁有"src"這個屬性的標籤都擁有跨域的能力),不會影響到被引用文件的安全,所以通過script標籤引入的js是不受同源策略的限制的。而通過ajax加載的文件內容是能夠被客戶端js獲取到的,所以ajax必須遵循同源策略,否則被引入文件的內容會泄漏或者存在其他風險;
2,它的兼容性更好,在更加古老的瀏覽器中都可以運行,不需要XMLHttpRequest或ActiveX的支持;
3,JSON的純字符數據格式可以簡潔的描述複雜數據,易於處理這種格式的數據;
4,不需要多餘的中轉操作;

缺點:
1,一旦開始沒法取消停止,沒法重新開始,也沒法捕捉錯誤;
2,JSONP是一種腳本注入(Script Injection)行為,所以有一定的安全隱患;
3,只能用GET請求;

思路:
1,在客戶端先聲明回調函數例如callbackName,然後插入一個script標籤指向請求地址同時傳入回調函數名字進行跨域請求數據;
2,服務端接收到之後生成json數據,然後以入參的方式放置到一個函數名為callbackName的function,再把這段js文檔返回給客戶端;
3,瀏覽器解析script標籤,並執行返回的javascript文檔,此時數據作為參數,執行客戶端預先聲明好的回調函數;

javascript 代碼

整個過程就像前端發個信息給後台說麻煩返回一個這種格式的js代碼給我,然後後台返回瀏覽器解析執行了這段代碼
javascript 代碼

在jQuery里用法如下
javascript 代碼

六,CORS(Cross-Origin Resource Sharing)跨域資源共享

原理:
跨域資源共享標準新增了一組 HTTP 首部字段,允許服務器聲明哪些源站有權限訪問哪些資源。另外,規範要求,對那些可能對服務器數據產生副作用的 HTTP 請求方法(特別是 GET 以外的 HTTP 請求,或者搭配某些 MIME 類型的 POST 請求),瀏覽器必須首先使用 OPTIONS 方法發起一個預檢請求(preflight request),從而獲知服務端是否允許該跨域請求。服務器確認允許之後,才發起實際的 HTTP 請求。在預檢請求的返回中,服務器端也可以通知客戶端,是否需要攜帶身份憑證(包括 Cookies 和 HTTP 認證相關數據)。.

優點:
1,CORS支持所有類型的HTTP請求;
2,CORS使用普通的XMLHttpRequest發起請求和獲得數據,可以捕捉到錯誤處理;
3,所有瀏覽器都支持CORS,IE瀏覽器不得低於10(IE8/9需要使用XDomainRequest對象來支持CORS);

前端跨域之原因&&方案&&原理

因為瀏覽器基本支持,前端代碼差別不大,主要實現細節都在瀏覽器和服務器,想要了解更多細節,請看
阮一峰老師的跨域資源共享 CORS 詳解
網上參考的利用CORS實現跨域請求
很詳細的文章HTTP訪問控制(CORS)

因為第二篇文章的實例寫的比我好多了,主要是做好兼容跟容錯,我就放出他的代碼好了,請允許我偷個懶
javascript 代碼

评论 ( 0 )
最新评论
暂无评论

赶紧努力消灭 0 回复