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