在客戶端編程語言中,如JavaScript和ActionScript,同源策略是一個很重要的安全理念,它在保證數(shù)據(jù)的安全性方面有著重要的意義。同 源策略規(guī)定跨域之間的腳本是隔離的,一個域的腳本不能訪問和操作另外一個域的絕大部分屬性和方法。那么什么叫相同域,什么叫不同的域呢? 同源策略在客戶端編程語言中,如JavaScript和ActionScript,同源策略是一個很重要的安全理念,它在保證數(shù)據(jù)的安全性方面有著重要的意義。同源策略規(guī)定跨域之間的腳本是隔離的,一個域的腳本不能訪問和操作另外一個域的絕大部分屬性和方法 那么什么叫相同域,什么叫不同的域呢?當兩個域具有相同的協(xié)議(如http), 相同的端口(如80),相同的host(如www.example.org),那么我們就可以認為它們是相同的域。比如http://www.example.org/和http://www.example.org/sub/是同域,而http://www.example.org, https://www.example.org, http://www.example.org:8080, http://sub.example.org中的任何兩個都將構(gòu)成跨域。同源策略還應(yīng)該對一些特殊情況做處理,比如限制file協(xié)議下腳本的訪問權(quán)限。本地的HTML文件在瀏覽器中是通過file協(xié)議打開的,如果腳本能通過file協(xié)議訪問到硬盤上其它任意文件,就會出現(xiàn)安全隱患,目前IE8還有這樣的隱患。 受到同源策略的影響,跨域資源共享就會受到制約。但是隨著人們的實踐和瀏覽器的進步,目前在跨域請求的技巧上,有很多寶貴經(jīng)驗的沉淀和積累。這里我把跨域資源共享分成兩種,一種是單向的數(shù)據(jù)請求,還有一種是雙向的消息通信。接下來我將羅列出常見的一些跨域方式,以下跨域?qū)嵗脑创a可以從這里獲得。 單向跨域JSONPJSONP (JSON with Padding)是一個簡單高效的跨域方式,HTML中的script標簽可以加載并執(zhí)行其他域的JavaScript,于是我們可以通過script標記來動態(tài)加載其他域的資源。例如我要從域A的頁面pageA加載域B的數(shù)據(jù),那么在域B的頁面pageB中我以JavaScript的形式聲明pageA需要的數(shù)據(jù),然后在pageA中用script標簽把pageB加載進來,那么pageB中的腳本就會得以執(zhí)行。JSONP在此基礎(chǔ)上加入了回調(diào)函數(shù),pageB加載完之后會執(zhí)行pageA中定義的函數(shù),所需要的數(shù)據(jù)會以參數(shù)的形式傳遞給該函數(shù)。JSONP易于實現(xiàn),但是也會存在一些安全隱患,如果第三方的腳本隨意地執(zhí)行,那么它就可以篡改頁面內(nèi)容,截獲敏感數(shù)據(jù)。但是在受信任的雙方傳遞數(shù)據(jù),JSONP是非常合適的選擇。 Flash URLLoaderFlash有自己的一套安全策略,服務(wù)器可以通過crossdomain.xml文件來聲明能被哪些域的SWF文件訪問,SWF也可以通過API來確定自身能被哪些域的SWF加載。當跨域訪問資源時,例如從域www.a.com請求域www.b.com上的數(shù)據(jù),我們可以借助Flash來發(fā)送HTTP請求。首先,修改域www.b.com上的crossdomain.xml(一般存放在根目錄,如果沒有需要手動創(chuàng)建) ,把www.a.com加入到白名單。其次,通過Flash URLLoader發(fā)送HTTP請求,最后,通過Flash API把響應(yīng)結(jié)果傳遞給JavaScript。Flash URLLoader是一種很普遍的跨域解決方案,不過需要支持iOS的話,這個方案就無能為力了。 Access ControlAccess Control是比較超越的跨域方式,目前只在很少的瀏覽器中得以支持,這些瀏覽器可以發(fā)送一個跨域的HTTP請求(Firefox, Google Chrome等通過XMLHTTPRequest實現(xiàn),IE8下通過XDomainRequest實現(xiàn)),請求的響應(yīng)必須包含一個Access-Control-Allow-Origin的HTTP響應(yīng)頭,該響應(yīng)頭聲明了請求域的可訪問權(quán)限。例如www.a.com對www.b.com下的asset.php發(fā)送了一個跨域的HTTP請求,那么asset.php必須加入如下的響應(yīng)頭: header("Access-Control-Allow-Origin: http://www.a.com"); window.namewindow對象的name屬性是一個很特別的屬性,當該window的location變化,然后重新加載,它的name屬性可以依然保持不變。那么我們可以在頁面A中用iframe加載其他域的頁面B,而頁面B中用JavaScript把需要傳遞的數(shù)據(jù)賦值給window.name,iframe加載完成之后,頁面A修改iframe的地址,將其變成同域的一個地址,然后就可以讀出window.name的值了。這個方式非常適合單向的數(shù)據(jù)請求,而且協(xié)議簡單、安全。不會像JSONP那樣不做限制地執(zhí)行外部腳本。 server proxy在數(shù)據(jù)提供方?jīng)]有提供對JSONP協(xié)議或者window.name協(xié)議的支持,也沒有對其它域開放訪問權(quán)限時,我們可以通過server proxy的方式來抓取數(shù)據(jù)。例如當www.a.com域下的頁面需要請求www.b.com下的資源文件asset.txt時,直接發(fā)送一個指向www.b.com/asset.txt的ajax請求肯定是會被瀏覽器阻止。這時,我們在www.a.com下配一個代理,然后把ajax請求綁定到這個代理路徑下,例如www.a.com/proxy/, 然后這個代理發(fā)送HTTP請求訪問www.b.com下的asset.txt,跨域的HTTP請求是在服務(wù)器端進行的,客戶端并沒有產(chǎn)生跨域的ajax請求。這個跨域方式不需要和目標資源簽訂協(xié)議,帶有侵略性,另外需要注意的是實踐中應(yīng)該對這個代理實施一定程度的保護,比如限制他人使用或者使用頻率。 雙向跨域document.domain通過修改document的domain屬性,我們可以在域和子域或者不同的子域之間通信。同域策略認為域和子域隸屬于不同的域,比如www.a.com和sub.a.com是不同的域,這時,我們無法在www.a.com下的頁面中調(diào)用sub.a.com中定義的JavaScript方法。但是當我們把它們document的domain屬性都修改為a.com,瀏覽器就會認為它們處于同一個域下,那么我們就可以互相調(diào)用對方的method來通信了。 FIM – Fragment Identitier Messaging不同的域之間,JavaScript只能做很有限的訪問和操作,其實我們利用這些有限的訪問權(quán)限就可以達到跨域通信的目的了。FIM (Fragment Identitier Messaging)就是在這個大前提下被發(fā)明的。父窗口可以對iframe進行URL讀寫,iframe也可以讀寫父窗口的URL,URL有一部分被稱為frag,就是#號及其后面的字符,它一般用于瀏覽器錨點定位,Server端并不關(guān)心這部分,應(yīng)該說HTTP請求過程中不會攜帶frag,所以這部分的修改不會產(chǎn)生HTTP請求,但是會產(chǎn)生瀏覽器歷史記錄。FIM的原理就是改變URL的frag部分來進行雙向通信。每個window通過改變其他window的location來發(fā)送消息,并通過監(jiān)聽自己的URL的變化來接收消息。這個方式的通信會造成一些不必要的瀏覽器歷史記錄,而且有些瀏覽器不支持onhashchange事件,需要輪詢來獲知URL的改變,最后,URL在瀏覽器下有長度限制,這個制約了每次傳送的數(shù)據(jù)量。 Flash LocalConnection頁面上的雙向通信也可以通過Flash來解決,F(xiàn)lash API中有LocalConnection這個類,該類允許兩個SWF之間通過進程通信,這時SWF可以播放在獨立的Flash Player或者AIR中,也可以嵌在HTML頁面或者是PDF中。遵循這個通信原則,我們可以在不同域的HTML頁面各自嵌套一個SWF來達到相互傳遞數(shù)據(jù)的目的了。SWF通過LocalConnection交換數(shù)據(jù)是很快的,但是每次的數(shù)據(jù)量有40kb的大小限制。用這種方式來跨域通信過于復雜,而且需要了2個SWF文件,實用性不強。 window.postMessagewindow.postMessage是HTML5定義的一個很新的方法,這個方法可以很方便地跨window通信。由于它是一個很新的方法,所以在很舊和比較舊的瀏覽器中都無法使用。 Cross FrameCross Frame是FIM的一個變種,它借助了一個空白的iframe,不會產(chǎn)生多余的瀏覽器歷史記錄,也不需要輪詢URL的改變,在可用性和性能上都做了很大的改觀。它的基本原理大致是這樣的,假設(shè)在域www.a.com上有頁面A.html和一個空白代理頁面proxyA.html, 另一個域www.b.com上有個頁面B.html和一個空白代理頁面proxyB.html,A.html需要向B.html中發(fā)送消息時,頁面會創(chuàng)建一個隱藏的iframe, iframe的src指向proxyB.html并把message作為URL frag,由于B.html和proxyB.html是同域,所以在iframe加載完成之后,B.html可以獲得iframe的URL,然后解析出message,并移除該iframe。當B.html需要向A.html發(fā)送消息時,原理一樣。Cross Frame是很好的雙向通信方式,而且安全高效,但是它在Opera中無法使用,不過在Opera下面我們可以使用更簡單的window.postMessage來代替。 總結(jié)跨域的方法很多,不同的應(yīng)用場景我們都可以找到一個最合適的解決方案。比如單向的數(shù)據(jù)請求,我們應(yīng)該優(yōu)先選擇JSONP或者window.name,雙向通信我們采取Cross Frame,在未與數(shù)據(jù)提供方?jīng)]有達成通信協(xié)議的情況下我們也可以用server proxy的方式來抓取數(shù)據(jù)。
|