跨域解決方案彙總

跨域解決方案彙總

同源策略

因爲瀏覽器的同源策略,若是兩個頁面的協議,端口(若是有指定)和域名都相同,則兩個頁面具備相同的源,只要有一者不一樣,就會形成跨域javascript

jsonp

jsonp由兩部分組成:回調函數和數據。回調函數是當響應到來時應該在頁面中調用的函數。回調函數的名字通常是在請求中指定。而數據就是傳入回調函數中的JSON數據。形式以下:html

<script>
var localHandle = function(data){
    alert("返回的數據:" + data);
}
</script>

<script src="http://remoteserver.com/remote.js?name=value&callback=localHandle"></script>

remote.js代碼以下:前端

localHandler({"result":"我是遠程js帶來的數據"});

服務器根據請求裏的callback=localHandle知道了本地的回調函數名稱localHandle,查詢字符串爲name=value,而後服務器執行相對應的函數並返回數據給本地的localHandle回調函數。java

建議動態的建立script來查詢node

<script>
var localHandle = function(data){
    alert("返回的數據:" + data);
}

// 提供jsonp服務的url地址(不論是什麼類型的地址,最終生成的返回值都是一段javascript代碼)
var url = "http://remoteserver.com/remote.js?name=value&callback=localHandle";
// 建立script標籤,設置其屬性
var script = document.createElement('script');
script.setAttribute('src', url);
// 把script標籤加入head,此時調用開始
document.getElementsByTagName('head')[0].appendChild(script);
</script>

jsonp優勢

簡單易用,可以直接訪問響應文本,支持瀏覽器和服務器之間的雙向通訊json

jsonp缺點

  • 安全性不足。藉助JSONP有可能進行跨站請求僞造(CSRF)攻擊,當一個惡意網站使用訪問者的瀏覽器向服務器發送請求並進行數據變動時,被稱爲CSRF攻擊。因爲請求會攜帶cookie信息,服務器會認爲是用戶本身想要提交表單或者發送請求,而獲得用戶的一些隱私數據。
  • 錯誤緣由不易找。JSONP缺少錯誤處理機制,若是腳本注入成功後,就會調用回調函數,可是注入失敗後,沒有任何提示。這就意味着,當JSONP遇到40四、505或者其餘服務器錯誤時,你是沒法檢測出錯緣由的。咱們可以作的也只有超時,沒有收到響應,便認爲請求失敗,執行對應的錯誤回調。
  • 只能適用於get請求。只能使用GET請求就意味着不少限制,提交到服務器的數據量將受限於瀏覽器的最大URL長度。

jsonp封裝

/**
 * JSONP請求工具
 * @param url 請求的地址
 * @param data 請求的參數
 * @returns {Promise<any>}
 */
const request = ({url, data}) => {
  return new Promise((resolve, reject) => {
    // 處理傳參成xx=yy&aa=bb的形式
    const handleData = (data) => {
      const keys = Object.keys(data)
      const keysLen = keys.length
      return keys.reduce((pre, cur, index) => {
        const value = data[cur]
        const flag = index !== keysLen - 1 ? '&' : ''
        return `${pre}${cur}=${value}${flag}`
      }, '')
    }
    // 動態建立script標籤
    const script = document.createElement('script')
    // 接口返回的數據獲取
    window.jsonpCb = (res) => {
      document.body.removeChild(script)
      delete window.jsonpCb
      resolve(res)
    }
    script.src = `${url}?${handleData(data)}&cb=jsonpCb`
    document.body.appendChild(script)
  })
}
// 使用方式
request({
  url: 'http://localhost:9871/api/jsonp',
  data: {
    // 傳參
    msg: 'helloJsonp'
  }
}).then(res => {
  console.log(res)
})

空iframe加form

const requestPost = ({url, data}) => {
  // 首先建立一個用來發送數據的iframe.
  const iframe = document.createElement('iframe')
  iframe.name = 'iframePost'
  iframe.style.display = 'none'
  document.body.appendChild(iframe)
  const form = document.createElement('form')
  const node = document.createElement('input')
  // 註冊iframe的load事件處理程序,若是你須要在響應返回時執行一些操做的話.
  iframe.addEventListener('load', function () {
    console.log('post success')
  })

  form.action = url
  // 在指定的iframe中執行form
  form.target = iframe.name;   //target規定在何處打開 action URL。這裏能夠經過iframe.name來指定iframe
  form.method = 'post'
  for (let name in data) {
    node.name = name
    node.value = data[name].toString()
    form.appendChild(node.cloneNode())
  }
  // 表單元素須要添加到主文檔中.
  form.style.display = 'none'
  document.body.appendChild(form)
  form.submit()

  // 表單提交後,就能夠刪除這個表單,不影響下次的數據發送.
  document.body.removeChild(form)
}
// 使用方式
requestPost({
  url: 'http://localhost:9871/api/iframePost',
  data: {
    msg: 'helloIframePost'
  }
})

document.domain + iframe

經過document.domain將兩個頁面的域名都設置成相同域名,也能夠實現跨域。 不過這個方法只適用於不一樣子域的框架間的交互api

// a.html
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
    document.domain = 'domain.com';
    var user = 'admin';
</script>
// b.html
<script>
    document.domain = 'domain.com';
    // 獲取父窗口中變量
    alert('get js data from parent ---> ' + window.parent.user);
</script>

location.hash

經過location.hash來跨域,原理是改變url的hash部分來進行雙向通訊,父頁面向iframe子頁面通訊只需監聽自身的url變化來發送信息,而子頁面向父頁面通訊就麻煩一些,因爲兩個頁面不在同一個域,IE和Chrome都不容許修改parent.location.hash的值,因此須要建立一個和父頁面同域的中間頁,中間頁可利用parent.parent訪問父頁面的全部對象。跨域

該方法的缺點是會形成沒必要要的瀏覽器歷史記錄,而且有些瀏覽器不支持onhashchange事件,數據直接暴露在url中,數據容量和類型都有限等。瀏覽器

// a.html
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');

    // 向b.html傳hash值
    setTimeout(function() {
        iframe.src = iframe.src + '#user=admin';
    }, 1000);
    
    // 開放給同域c.html的回調方法
    function onCallback(res) {
        alert('data from c.html ---> ' + res);
    }
</script>
// b.html
<iframe id="iframe" src="http://www.domain1.com/c.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');

    // 監聽a.html傳來的hash值,再傳給c.html
    window.onhashchange = function () {
        iframe.src = iframe.src + location.hash;
    };
</script>
// c.html
<script>
    // 監聽b.html傳來的hash值
    window.onhashchange = function () {
        // 再經過操做同域a.html的js回調,將結果傳回
        window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));
    };
</script>

window.name + iframe

window.name + iframe,利用window.name在不一樣頁面(甚至不一樣域)加載後依舊存在,而且支持大小達到了2MB。 步驟: 首先在a頁面中建立iframe,將src指向外域保存數據到window.name,再將iframe的src指向和a頁面同域的b代理頁面,藉此讓a頁面以iframe.contentWindow.name的形式成功讀取數據。安全

//a.html
var proxy = function(url, callback) {
    var state = 0;
    var iframe = document.createElement('iframe');

    // 加載跨域頁面
    iframe.src = url;

    // onload事件會觸發2次,第1次加載跨域頁,並留存數據於window.name
    iframe.onload = function() {
        if (state === 1) {
            // 第2次onload(同域proxy頁)成功後,讀取同域window.name中數據
            callback(iframe.contentWindow.name);
            destoryFrame();

        } else if (state === 0) {
            // 第1次onload(跨域頁)成功後,切換到同域代理頁面
            iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';
            state = 1;
        }
    };

    document.body.appendChild(iframe);

    // 獲取數據之後銷燬這個iframe,釋放內存;這也保證了安全(不被其餘域frame js訪問)
    function destoryFrame() {
        iframe.contentWindow.document.write('');
        iframe.contentWindow.close();
        document.body.removeChild(iframe);
    }
};

// 請求跨域b頁面數據
proxy('http://www.domain2.com/b.html', function(data){
    alert(data);
});
//b.html
<script>
    window.name = 'This is domain2 data!';
</script>

postMessage跨域

postMessage是HTML5新增的window屬性

用法:postMessage(data,origin)方法接受兩個參數

//a.html
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>       
    var iframe = document.getElementById('iframe');
    iframe.onload = function() {
        var data = {
            name: 'aym'
        };
        // 向domain2傳送跨域數據
        iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
    };

    // 接受domain2返回數據
    window.addEventListener('message', function(e) {
        alert('data from domain2 ---> ' + e.data);
    }, false);
</script>
//b.html
<script>
    // 接收domain1的數據
    window.addEventListener('message', function(e) {
        alert('data from domain1 ---> ' + e.data);

        var data = JSON.parse(e.data);
        if (data) {
            data.number = 16;

            // 處理後再發回domain1
            window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
        }
    }, false);
</script>

CORS

CORS須要瀏覽器和服務器同時支持。目前,全部瀏覽器都支持該功能,IE瀏覽器不能低於IE10。

整個CORS通訊過程當中,都是瀏覽器自動完成,對於開發者來講,CORS和同源的AJAX通訊沒有差異,瀏覽器一旦發現AJAX跨域,就會自動添加一些附加的頭部信息,根據請求的不一樣還會多出一次附加的請求。

實現CORS通訊的關鍵是服務器,只要服務器實現了CORS接口,就實現了跨域

服務器實現CORS通訊的關鍵是設置如下請求頭

  1. Access-Control-Allow-Origin,
    該字段時必選的,用於設置容許響應資源的origin
Access-Control-Allow-Origin: *  //容許全部域
Access-Control-Allow-Origin: <origin>   容許指定的origin
  1. Access-Control-Allow-Credentials 該字段是可選的,用於設置是否容許發送Cookie,該字段的值只能設爲布爾值 ==true== ,刪除該字段便可不發送。若是須要發送Cookie,這裏還須要前端的AJAX請求設置 ==withCredentials== 屬性,此外,Access-Control-Allow-Origin不能設爲星號,必須明確指定與請求網頁一致的域名,而且,Cookie依舊遵循同源策略,只有用服務器域名設置的Cookie纔會上傳,其餘域名的Cookie並不會上傳,且(跨源)原網頁代碼中的document.cookie也沒法讀取服務器域名下的Cookie。
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
  1. Access-Control-Expose-Header
    該字段可選,XMLHttpRequest對象的getResponseHeader()方法只能拿到6個基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。若是想拿到其餘字段,就必須在Access-Control-Expose-Headers裏面指定。

CORS與JSONP比較

CORS與JSONP的使用目的相同,可是比JSONP更強大。

JSONP只支持GET請求,CORS支持全部類型的HTTP請求。JSONP的優點在於支持老式瀏覽器,以及能夠向不支持CORS的網站請求數據。

相關文章
相關標籤/搜索