Web開發中跨域的幾種解決方案

同domain(或ip),同端口,同協議視爲同一個域,一個域內的腳本僅僅具備本域內的權限,能夠理解爲本域腳本只能讀寫本域內的資源,而沒法訪問其它域的資源。這種安全限制稱爲同源策略。
出於安全考慮,HTML的同源策略不容許JavaScript進行跨域操做, 直接發送跨域 AJAX 會獲得以下錯誤:面試

XMLHttpRequest cannot load http://b.com. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://a.com' is therefore not allowed access.

隨着Web App的功能愈來愈強 各類跨域的需求催生了無數的跨域手法。甚至在HTML5標準中都給出了官方的跨域方法, 也是最近應付面試的須要,拿一篇文章來總結既有的各類跨域手段。json

這些跨域通訊的方法大體能夠分爲兩類:跨域

  • 一類是Hack,好比經過title, navigation等對象傳遞信息,JSONP能夠說是一個最優秀的Hack。
  • 另外一類是HTML5支持,一個是Access-Control-Allow-Origin響應頭,一個是window.postMessage。
設置 document.domain
  • 原理:相同主域名不一樣子域名下的頁面,能夠設置document.domain讓它們同域
  • 限制:同域document提供的是頁面間的互操做,須要載入iframe頁面

下面幾個域名下的頁面都是能夠經過document.domain跨域互操做的: http://a.com/foo, http://b.a.com/bar, http://c.a.com/bar。 但只能以頁面嵌套的方式來進行頁面互操做,好比常見的iframe方式就能夠完成頁面嵌套:瀏覽器

// URL http://a.com/foo
var ifr = document.createElement('iframe');
ifr.src = 'http://b.a.com/bar'; 
ifr.onload = function(){
    var ifrdoc = ifr.contentDocument || ifr.contentWindow.document;
    ifrdoc.getElementsById("foo").innerHTML);
};

ifr.style.display = 'none';
document.body.appendChild(ifr);

上述代碼所在的URL是http://a.com/foo,它對http://b.a.com/bar的DOM訪問要求後者將 document.domain往上設置一級:安全

// URL http://b.a.com/bar
document.domain = 'a.com'

document.domain只能從子域設置到主域,往下設置以及往其餘域名設置都是不容許的, 在Chrome中給出的錯誤是這樣的:服務器

Uncaught DOMException: Failed to set the 'domain' property on 'Document': 'baidu.com' is not a suffix of 'b.a.com'
有src的標籤
  • 原理:全部具備src屬性的HTML標籤都是能夠跨域的,包括<img>, <script>
  • 限制:須要建立一個DOM對象,只能用於GET方法

在document.body中append一個具備src屬性的HTML標籤, src屬性值指向的URL會以GET方法被訪問,該訪問是能夠跨域的。app

其實樣式表的 標籤也是能夠跨域的,只要是有src或href的HTML標籤都有跨域的能力。
不一樣的HTML標籤發送HTTP請求的時機不一樣,例如在更改src屬性時就會發送請求,而script, iframe, link[rel=stylesheet]只有在添加到DOM樹以後纔會發送HTTP請求:cors

var img = new Image();
img.src = 'http://some/picture';        // 發送HTTP請求

var ifr = $('<iframe>', {src: 'http://b.a.com/bar'});
$('body').append(ifr);                  // 發送HTTP請求
JSONP
  • 原理:<script>是能夠跨域的,並且在跨域腳本中能夠直接回調當前腳本的函數。
  • 限制:須要建立一個DOM對象而且添加到DOM樹,只能用於GET方法
    JSONP利用的是<script>能夠跨域的特性,跨域URL返回的腳本不只包含數據,還包含一個回調:
// URL: http://b.a.com/foo
var data = {
    foo: 'bar',
    bar: 'foo'
};
callback(data);

該例子只用於示例,實際狀況應當考慮名稱隱藏等問題。
而後在咱們在主站http://a.com中,能夠這樣來跨域獲取http://b.a.com的數據:dom

// URL: http://a.com/foo
var callback = function(data){
    // 處理跨域請求獲得的數據
};
var script = $('<script>', {src: 'http://b.a.com/bar'});
$('body').append(script);

其實jQuery已經封裝了JSONP的使用,咱們能夠這樣來:函數

$.getJSON( "http://b.a.com/bar?callback=callback", function( data ){
    // 處理跨域請求獲得的數據
});

$.getJSON與$.get的區別是前者會把responseText轉換爲JSON,並且當URL具備callback參數時, jQuery將會把它解釋爲一個JSONP請求,建立一個<script>

和全部依賴於建立HTML標籤的方式同樣,JSONP也不支持POST,而GET的數據是放在URL裏的。通常來說URL限長是在2000字符左右。

在如何理解HTTP響應的狀態碼?一文中有更多關於HTTP響應狀態碼的討論。

  • 原理:iframe之間是共享navigator對象的,用它來傳遞信息
  • 要求:IE6/7

有些人注意到了IE6/7的一個漏洞:iframe之間的window.navigator對象是共享的。 咱們能夠把它做爲一個Messenger,經過它來傳遞信息。好比一個簡單的委託:

// a.com
navigation.onData(){
    // 數據到達的處理函數
}
typeof navigation.getData === 'function' 
    || navigation.getData()
// b.com
navigation.getData = function(){
    $.get('/path/under/b.com')
        .success(function(data){
            typeof navigation.onData === 'function'
                || navigation.onData(data)
        });
}

與document.navigator相似,window.name也是當前窗口全部頁面所共享的。也能夠用它來傳遞信息。 一樣蛋疼的辦法還有傳遞Hash(有些人叫錨點),這是由於每次瀏覽器打開一個URL時,URL後面的#xxx部分會保留下來,那麼新的頁面能夠從這裏得到上一個頁面的數據。

跨域資源共享(CORS)
  • 原理:服務器設置Access-Control-Allow-OriginHTTP響應頭以後,瀏覽器將會容許跨域請求
  • 限制:瀏覽器須要支持HTML5,能夠支持POST,PUT等方法

前面提到的跨域手段都是某種意義上的Hack, HTML5標準中提出的跨域資源共享(Cross Origin Resource Share,CORS)纔是正道。 它支持其餘的HTTP方法如PUT, POST等,能夠從本質上解決跨域問題。

例如,從http://a.com要訪問http://b.com的數據,一般狀況下Chrome會因跨域請求而報錯:

XMLHttpRequest cannot load http://b.com. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://a.com' is therefore not allowed access.

錯誤緣由是被請求資源沒有設置Access-Control-Allow-Origin,因此咱們在b.com的服務器中設置這個響應頭字段便可:

Access-Control-Allow-Origin: *              # 容許全部域名訪問,或者
Access-Control-Allow-Origin: http://a.com   # 只容許XXX域名訪問

爲 xhr 設置 withCredentials 後 CORS 方法跨域還可 攜帶Cookie,但 PUT/POST 請求須要注意處理 preflight 請求。

window.postMessage
  • 原理:HTML5容許窗口之間發送消息
  • 限制:瀏覽器須要支持HTML5,獲取窗口句柄後才能相互通訊
    這是一個安全的跨域通訊方法,postMessage(message,targetOrigin)也是HTML5引入的特性。

能夠給任何一個window發送消息,不管是否同源。第二個參數能夠是*但若是你設置了一個URL但不相符,那麼該事件不會被分發。看一個普通的使用方式吧:

// URL: http://a.com/foo
var win = window.open('http://b.com/bar');
win.postMessage('Hello, bar!', 'http://b.com');
// URL: http://b.com/bar
window.addEventListener('message',function(event) {
    console.log(event.data);
});

注意IE8及小於IE8的版本不支持addEventListener,須要使用attachEvent來監聽事件。 參見:事件處理中的this:attachEvent, addEventListener, onclick

訪問控制安全的討論

在HTML5以前,JSONP已經成爲跨域的事實標準了,jQuery都給出了支持。 值得注意的是它只是Hack,並無產生額外的安全問題。 由於JSONP要成功獲取數據,須要跨域資源所在服務器的配合,好比資源所在服務器須要自願地回調一個合適的函數,因此服務器仍然有能力控制資源的跨域訪問。

跨域的正道仍是要使用HTML5提供的CORS頭字段以及window.postMessage, 能夠支持POST, PUT等HTTP方法,從機制上解決跨域問題。 值得注意的是Access-Control-Allow-Origin頭字段是資源所在服務器設置的, 訪問控制的責任仍然是在提供資源的服務器一方,這和JSONP是同樣的。

相關文章
相關標籤/搜索