標籤:

跨了個域

跨了個域

來自專欄全家都創新9 人贊了文章如今是一個平台眾多,軟體開發平民化,軟體服務可拼湊成產品的時代。優秀的互聯網產品,像微博,微信,Github,Instagram都變成平台,提供一些優雅或不優雅的API讓靠譜或不靠譜的開發者來玩,這些API里肯定會涉及到跨域訪問的知識。靠譜的開發者即使遇到沒有開發文檔也能摸索出套路,反之就需要了解下跨域訪問的知識了。

為什麼需要「踩過界」

跨域訪問指的是不同源的A和B網站,A請求訪問B的資源。資源包括B網站的圖片、視頻、數據、文件等等。例如,新浪微博提供的微博API就是提供給不同源網站訪問微博數據的介面;又如,A網站嵌入了B網站的圖片或視頻資源也屬於跨域訪問。

那麼,為什麼不能直接訪問呢?等等,讓我們先來聊聊瀏覽器同源策略(Same-Origin Policy),同源策略限制了本網站的腳本不能對其他源的網站資源進行讀和寫操作。兩個域必須滿足協議(protocol)、埠(port)、主地址(host)都是一致的,才能相互請求資源。

假設有不同源的源A和源B,源A對源B的讀操作指的是:

  • 讀取源B的JavaScript,CSS的源代碼(受到限制)
  • 讀取源B的返回的文檔,JSON等數據(受到限制)
  • 讀取源B的圖片的二進位數據(受到限制)

源A對源B的寫操作指的是源A發送數據到源B,主要包括:

  • 源A對源B進行表單,Ajax(XMLHttpRequest或XDomainRequest)POST方式提交
  • 源A嵌入指向源B的鏈接,點擊後發生的跳轉
  • 源A的腳本操作嵌入到iframe中的源B的DOM對象(受到限制)
  • 源A使用postMessage發送嵌入到iframe中的的源B

以上讀和寫操作,加上了(受到限制)的操作都屬於瀏覽器同源策略限制。

其中最重要的危害最大的是寫操作中的對源B的DOM對象進行操作。就以支付寶做一個例子,作為攻擊者,我申請了一個網址叫做alipay.tb.com,整個網站沒有內容就一個隱藏的iframe嵌入了alipay.com,因為支付寶會記錄登陸用戶的cookie為了讓用戶不用每次訪問都需要登陸,在沒有同源策略的保護下,我可以控制你支付寶,獲取你的餘額記錄和消費記錄(此時沒有了讀取文檔限制)或直接利用腳本幫我進行支付,而用戶不會洞察到什麼,只是收到了消費的簡訊通知,錢財不翼而飛,損失很大。可見在沒有同源策略下的互聯網是多麼的恐怖。不過放心,即使沒有同源策略我們的支付寶也沒有這麼弱,支付時還需要支付密碼。

不過光有瀏覽器同源策略是不能完全阻止腳本在你已經登陸的網站上肆虐操作,如果用戶主動點擊了頁面上載入不安全腳本的鏈接,不安全腳本也會趁虛而入,我們把這種攻擊叫做XSS(Cross Site Scripting)。目前可能遇見的方式有這麼幾種:1、點擊了鏈接載入不安全腳本,2、保存成bookmark的腳本不安全,3、Chrome插件被注入惡意腳本。這裡就有個血淋淋的新浪微博遭到XSS攻擊的例子 。

在讀取操作中有一項限制很有趣,就是不同源之間讀取文檔,JSON數據是受限制的。提供API就是為了讓不同源之間可以相互訪問,但是因為同源策略的存在,源B的JSON數據不能直接被讀取到,導致現在出現了各種各樣的跨域訪問方式,例如JSONP,CORS(Cross Origin Resource Sharing),後台代理等。

跨域訪問需要跨越的就是同源策略的多個限制。現在我們來看看幾種使用廣泛的跨域訪問方式是如何工作的。

跨域請求方式

JSONP

JSONP(JSON with padding)是目前使用的最廣,最簡單的一種跨域訪問方式。既然說同源策略限制了腳本於不同源網站資源進行交互,那麼JSONP的原理是什麼呢?其實同源策略中還存在另一種腳本和資源的交互方式,叫做嵌入(Embed)方式。也就是說,源A可以嵌入源B的資源,比如嵌入源B中的圖片、樣式文件、腳本文件。樣式文件和腳本文件嵌入之後會被運行。JSONP就是利用了網站可以嵌入腳本並運行這一點。

讓我們來看一個例子。定義了簡單的後端HTTP GET服務,每次請求返回一段腳本代碼,並且把後段數據「hello world」發送回去。

def jsonp str = hello world func = params[:callback] render :js => "try{" + "#{func}({ str : #{str}});" + "}catch(e){" + "console.error(request error);" + "}"end

實現一個簡單的前端調用,使用的是jQuery的getJSON方法

$.getJSON(http://localhost:3000/jsonp?callback=?, function (ret) { console.log(ret.str); // output "hello world"});

在調用getJSON方法之後發送了callback參數告訴後端生產的JavaScript代碼需要調用這個方法,並且把後端的資源「hello world」準備成該方法的一個參數返回回來,達到跨域訪問的目的。為了更仔細觀察整個過程,我們來看看HTTP的包是什麼樣子的。

// RequestGET /jsonp?callback=jQuery19008820020072162151_1359426298971 HTTP/1.1Host: localhost:3000Connection: keep-aliveCache-Control: max-age=0…// ResponseHTTP/1.1 200 OKContent-Type: text/javascript; charset=utf-8Etag: "a60520e11c6991b89099c84cb4742f39」Cache-Control: max-age=0, private, must-revalidateContent-Length: 112…try{jQuery19008820020072162151_1359426298971({ str : hello world });}catch(e){console.error(request error);}

需要注意的是jQuery為我們準備的方法叫做jQuery19008820020072162151_1359426298971,這和response內容中的方法調用對應。另一個需要注意的是,response的Content-Type為text/javascript; charset=utf-8,在調用方來看想要得到的是JSON數據,不是腳本,response中的描述出現了「錯誤」。

JSONP方式請求,實質上是一個對腳本的GET請求,它不像Ajax請求那樣存在載入,調用完成等狀態;安全性也不是特別高,存在載入不安全的腳本在本站運行具有風險。jQuery提供的getJSON這樣的方式讓開發者掉用起來不會感覺和Ajax調用有多大差別。雖然它並不完善,但它的易用性使它現在適用範圍最廣的跨域訪問方式,目前幾乎所有的API都會準備JSONP調用方式。

CORS

CORS(Corss Origin Resource Sharing)跨域資源共享是真正用來解決資源共享問題。不像JSONP那麼旁門左道,它是通過HTTP方式來實現資源共享,讓每個請求的服務直接返回資源。它使用了HTTP交互方式來確定請求源是否有資格請求該資源,並且通過設置HTTP Header來控制訪問資源的許可權。

我們來看一個例子。同樣還是定義後端服務,返回的資源仍然是「hello world」。

def cors response.headers[Access-Control-Allow-Origin] = "*" response.headers[Access-Control-Allow-Methods] = "GET" response.headers[Access-Control-Max-Age] = 60 ret = { :str => hello world } render :json => ret.to_jsonend

看到了一些HTTP Header的配置,過會再來分析它們是什麼。接著,使用jQuery簡單實現前端調用。

$.getJSON(http://localhost:3000/cors, function (ret) { console.log(ret.str); // output "hello world"});

雖然結果是一樣的,但其實其中經過了很多過程,我們看看HTTP請求過程就知道。第一次請求後端時候,瀏覽器意識到是訪問一個跨與資源,沒有直接發送GET請求獲取數據,而是發送了一個OPTIONS請求詢問是否可以訪問該資源。我們稱之為Preflight請求。

OPTIONS /cors HTTP/1.1Host: localhost:3000Origin: http://localhost:8000Accept: application/json, text/javascript, */*; q=0.01Access-Control-Request-Method: GET…

瀏覽器嘗試用GET方式請求資源localhost:3000/cors,默認因為同源策略的存在,肯定是請求失敗的,所以經常看到的一個錯誤XMLHttpRequest cannot load http://localhost:3000/. Origin XXX is not allowed by Access-Control-Allow-Origin. 就會出現。這個例子中,Preflight請求的到的response是

HTTP/1.1 200 OKDate: Mon, 01 Dec 2013 01:15:39 GMTServer: Apache/2.0.61 (Unix)Access-Control-Allow-Origin: *Access-Control-Allow-Methods: GETAccess-Control-Max-Age: 60Content-Encoding: gzipContent-Length: 0Connection: Keep-AliveContent-Type: text/text…

通過response,伺服器告知瀏覽器所有域都可以使用GET方式請求請求該資源,有效時間為60秒。得到回復後,瀏覽器自動再次發出真正的請求並且得到真正的資源。

// Actual RequestGET /cors HTTP/1.1Host: localhost:3000Origin: http://localhost:8000Accept: application/json, text/javascript, */*; q=0.01Referer: http://localhost:8000/temp.htmlAccept-Encoding: gzip,deflate,sdchAccept-Language: en-US,en;q=0.8…// Return ResourceHTTP/1.1 200 OKAccess-Control-Allow-Origin: *Access-Control-Allow-Methods: GETAccess-Control-Max-Age: 60Content-Type: application/json; charset=utf-8Content-Length: 24…{ "str" : "hello world" }

和JSONP方式不同,資源最終返回到請求方時並不是腳本代碼,而且資源本省,response包的Content-Type也正確地對資源進行了描述。雖然是兩次請求,但是瀏覽器自動處理,開發者並不需要做其他處理。BTW,現在還有很多人分不清楚JSON數據對象和JavaScript對象的聯繫和區別。

CORS使用HTTP Header來控制資源的可訪問性,可以使用的屬性主要有:

Access-Control-Allow-Origin: <origin> | * // 授權的源控制Access-Control-Max-Age: <delta-seconds> // 授權的時間Access-Control-Allow-Credentials: true | false // 控制是否開啟與Ajax的Cookie提交方式Access-Control-Allow-Methods: <method>[, <method>]* // 允許請求的HTTP MethodAccess-Control-Allow-Headers: <field-name>[, <field-name>]* // 控制哪些header能發送真正的請求

CORS方式我很喜歡,目前支持的瀏覽器有Chrome 4+, FF 3.5+, IE 8+, Opera 12+, Safari 4+,雖然不是全都支持,但是這種優雅的方式會漸漸取代JSONP成為主流,目前發現的Github, W3C文檔都已經實現。

後端請求

後端請求沒有存在同源策略,因為後端請求不會發送Cookie到後端,不存在登陸過的網站Cookie被其他域的網站調用的情況,所以後端請求也經常用來作為跨域訪問。有時提供資源方只讓授權的第三方請求數據,往往會給予一個token個第三方,作為請求時的驗證信息。這種做法是非常安全的。

前端需要顯示跨域資源時,往往時發送請求到自己的後端,通過後端請求到跨域資源並且返回,像代理一樣工作。比起前兩種,無疑需要更多的開發工作去設計並實現包裝的介面。此方式比較簡單,我們看一個請求新浪微博的例子。

import httplibconn = httplib.HTTPSConnection("api.weibo.com")conn.request("GET", "/2/statuses/user_timeline.json?source=3168xxx&uid=264xxx")response = conn.getresponse()print response.read()conn.close()

其他方式

當然還有很多跨域方式,在這就不一一詳細介紹了:

  • 域與子域之間的跨域訪問通過document.domain來解決
  • 新的HTML5中window.postMessage API可以讓消息在多個窗口中傳遞
  • 還有之前flash使用的crossdomain.xml文件

更多理論的介紹可以去Google上搜索,不過還是推薦幾篇文章給大家,Same-origin Policy – MDN,Same Origin Policy Part1 – No Peeking,Same Origin Policy Part2 – Limited Write,CORS – MDN。


推薦閱讀:

努力讓自己更好
Lottie這次更新你應該了解一下
前端教程,web前端教程全集,學完你就是大神!
node函數 node路由
淺談使用 Vue 構建前端 10W+ 代碼的單頁面應用(一)

TAG:前端開發 |