跨域解決方案彙總

最新博客站點:歡迎來訪javascript

1、同源與同源策略html

       咱們知道,同源指的是協議、域名、端口號所有相同。同源策略(Same Origin Policy)是一種約定,它是瀏覽器最核心也是最基本的安全功能,若是缺乏了同源策略,則瀏覽器的正常功能均可能會受到影響。Web是構建在同源策略基礎之上的,瀏覽器只是針對同源策略的一種實現。同源策略是處於對用戶安全的考量的,若是缺乏了同源的限制,那又怎麼可以肯定別人的網站始終對你是友好的呢。針對非同源的狀況制定了一些限制條件,1. 沒法讀取不一樣源的cookie、LocalStorage、indexDB。2. 沒法得到不一樣源的DOM。3. 不能向不一樣源的服務器發送Ajax請求。前端

  在瀏覽器中,<script><img><iframe><link>等標籤均可以跨域加載資源,而不受同源策略的限制。事實上,在大多數情境下,咱們常常是須要借用非同源來提供數據的,因此這就要用到跨域方面的技術了。java

2、JSONPajax

       JSONP是指JSON Padding,JSONP是一種非官方跨域數據交換協議,因爲script的src屬性能夠跨域請求,因此JSONP利用的就是瀏覽器的這個原理,須要通訊時,動態插入一個javascript標籤。請求的地址通常帶有一個callback參數,假設須要請求的地址爲http://localhost:3000?callback=show,服務器返回的代碼通常是show()的JSON數據,而show函數偏偏是前端須要用的這個數據的函數。JSONP很是簡單易用,自動補全API利用的就是JSONP。json

        一個簡單的例子:segmentfault

var script = doxument.createElement("script");
script.setAttribute("type", "text/javascript");
script.src="http://example.com/ip?callback=handleResponse";
document.body.appendChild(script);

function handleResponse(data) {
    console.log('Your public IP address is: '+data.ip);
}

       JSONP解決跨域的本質:<script>標籤能夠請求不一樣域名下的資源,即<script>請求不受瀏覽器同源策略的影響。上例中的script會向http://example.com/服務器發送請求,這個請求的url後面帶了個callback參數,是用來告訴服務器回調方法的方法名的。由於服務器收到請求後,會把相應的數據寫進handleResponse的參數,也就是服務器會返回以下的腳本:後端

handleResponse({
    "ip" : "8.8.8.8"
});

       這樣瀏覽器經過<script>下載的資源就是上面的腳本了,<script>下載完就會當即執行,也就是說http://example.com/ip?callback=handleResponse這個請求返回後就會當即執行上面的腳本代碼,而這個腳本代碼就是調用回調方法和拿到json數據了。跨域

       咱們再來看一個例子:瀏覽器

//請求代碼
function jsonp(callback) {
    var script = document.createElement("script");
        url = `https://localhost:3000?callback=${callback}`;
    script.setAttribute("src", url);
    document.querySelector("head").appendChild(script);
}
function show(data) {
    concole.log(`學生姓名爲: ${data.name},年齡爲: ${data.age},性別爲: ${data.sex}`);
}
jsonp("show");
//響應代碼
const student = {
    name: "Knight",
    age: 19,
    sex: "male"
};
var callback = url.parse(req.url, true).query.callback;
res.writeHead(200,{
    "Content-Type": "application/json;charset=utf-8"
});
res.end(`${callback}(${JSON.stringify(student)})`);

  JSONP有一個很大問題,就是隻能進行GET請求。

3、跨域源資源共享(CORS)

  CORS是W3C制定的跨站資源分享標準,可讓AJAX實現跨域訪問,定義了在必須訪問跨域資源時瀏覽器與服務器該如何溝通。CORS背後的基本思想,就是使用自定義的HTTP頭部讓瀏覽器和服務器進行溝通,從而決定請求或響應應該成功仍是失敗。

        好比一個簡單的使用GET或POST的請求,它沒有自定義的頭部,而主體內容是text/plain。在發送該請求時,須要給它附加一個額外的Origin頭部,其中包含請求頁面的源信息(協議、域名、端口號),以便服務器根據該頭部信息來決定是否給予響應。

Origin: http://www.example.com

  若是服務器認爲這個請求能夠接受,就在Access-Control-Allow-Origin頭部中發回相同的源信息(若是是公共資源,能夠發「*」)。例如:

Access-Control-Allow-Origin: http://www.example.com

  若是沒有這個頭部信息或信息不匹配,瀏覽器就會駁回請求。正常狀況下,瀏覽器會處理請求。此時,請求和響應都不包含Cookie信息。

  簡單請求的跨域:

  請求方式爲GET或則POST;

  倘若請求是POST的話,Content-Type必須爲下列之一:

    application/x-www-form-urlencoded

    mutipart/form-data

       text/plain

  不含有自定義頭;

 對於簡單的跨域只進行一次http請求:

function ajaxPost(url, obj, header) {
    return new Promise((resolve, reject) => {
         var xhr=new XMLHttpRequest(),
              str = '' ;
              keys = Object.keys(obj);
         for(var i=0,len=keys.length;i<len;i++) {
             str +=`${keys[i]}=${obj[keys[i]]}&`;
         }
         str = str.substring(0, str.length - 1);
         xhr.open('post', url);
         xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
         if(header instanceof Object) {
             for(var k in header)
                  xhr.setRequestHeader(k, header[k]);
         }
         xhr.send(str);
         xhr.onreadystatechange = function() {
              if(xhr.readyState == 4) {
                  if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
                      resolve(xhr.responseText);
                  } else {
                      reject();
                  }
              }
         }
         
    });
}
ajaxPost("https://localhost:3000?page=cors", {
        name: "Knight",
        age: 19,
        sex: "male"
}).then((text) => {console.log(text);},
                ()=>{console.log("請求失敗");});
//後端處理
var postData = "";
req.on("data", (data) => {
    postData += data;
});
req.on("end", () => {
    postData = querystring.parse(postData);
    res.writeHead(200, {
        "Access-Control-Allow-Origin": "*",
        "Content-Type": "application/json;charset=utf-8"
    });
    if(postData.name === student.name && Number(postData.age) === student.age &&  postData.sex === student.sex) {
        res.end(`yeah! ${postData.name} is a good guy!`);
    } else {
        res.end("No! a bad guy!");
    }
});

對於非簡單請求來講,須要兩次http請求,其中在請求以前有一次預檢請求。

function ajaxPost(url, obj, header) {
    return new Promise((resolve, reject) => {
         var xhr=new XMLHttpRequest(),
              str = '' ;
              keys = Object.keys(obj);
         for(var i=0,len=keys.length;i<len;i++) {
             str +=`${keys[i]}=${obj[keys[i]]}&`;
         }
         str = str.substring(0, str.length - 1);
         xhr.open('post', url);
         xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
         if(header instanceof Object) {
             for(var k in header)
                  xhr.setRequestHeader(k, header[k]);
         }
         xhr.send(str);
         xhr.onreadystatechange = function() {
              if(xhr.readyState == 4) {
                  if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
                      resolve(xhr.responseText);
                  } else {
                      reject();
                  }
              }
         }
         
    });
}
ajaxPost("https://localhost:3000?page=cors", {
        name: "Knight",
        age: 19,
        sex: "male"
}, {"X-author": "Knight"}).then((text) => {console.log(text);},
                ()=>{console.log("Request Error!");});
//後端處理
var postData = "";
if(req.method == "OPTIONS") {
    res.writeHead(200, {
        "Access-Control-Max-Age": 3000,
        "Access-Control-Allow-Headers": "X-author",
        "Access-Control-Allow-Origin": "*",
        "Content-Type": "application/json;charset=utf-8"
    });
    res.end();
    return void 0;
}
req.on("data", (data) => {
    postData += data;
});
req.on("end", () => {
    postData = querystring.parse(postData);
    res.writeHead(200, {
        "Access-Control-Allow-Origin": "*",
        "Content-Type": "application/json;charset=utf-8"
    });
    if(postData.name === student.name && Number(postData.age) === student.age &&  postData.sex === student.sex) {
        res.end(`yeah! ${postData.name} is a good guy!`);
    } else {
        res.end("No! a bad guy!");
    }
});

  上面代碼中,兩個響應頭: Access-Control-Allow-Headers,用來指明在實際的請求中,可使用那些自定義的http請求頭;Access-Control-Max-Age,用來指定這次預請求的結果的有效期,在有效期內則不會發出預請求,相似於緩存。

4、document.domain實現跨域

  能夠將子域和主域的document.domian設爲同一個主域來實現跨域。但前提條件是,這兩個域名必須屬於同一個基礎域名,所用的協議,端口都要一致,不然沒法經過document.domain()來進行跨域。

        example 1:

        若是想要在你的http://www.knightboy.cn/a.html頁面裏使用<iframe>調用另外一個http://knightboy.cn/b.html頁面。這時候你想在a頁面裏面獲取b頁面裏的DOM,而後進行操做。而後你會發現你不能得到b的DOM。document.getElementById("myIFrame").contentWindow.document或window.parent.document.body由於兩個窗口不一樣源而報錯。

        這時候你只須要在a頁面裏和b頁面裏把document.domian設置成相同的值就能夠在兩個頁面裏操做Dom了。

        example 2:

        若是你在http://www.knightboy.cn/a.html頁面裏寫入了document.cookie = "test=hello world";你在http://knightboy.cn/b.html頁面是拿不到這個cookie的。

        緣由在於,Cookie是服務器寫入瀏覽器的一小段信息,只有同源的網頁才能共享。可是,兩個網頁一級域名相同,二級域名不一樣,瀏覽器容許經過設置document.domain來共享Cookie。另外,服務器也能夠在設置Cookie的時候,指定Cookie的所屬域名爲一級域名。這樣的話,二級域名和三級域名不用作任何設置即可以讀取這個Cookie。

        有一點須要注意的是:document.domain雖然能夠讀寫,但只能設置成自身或者是高一級的父域且主域必須相同。因此只能解決一級域名相同二級域名不一樣的跨域問題。還有就是document.domain只適用於Cookie和iframe窗口,LocalStorage和IndexDB沒法經過這種方法跨域。

5、window.name跨域

        window對象有一個name屬性,該屬性有個特徵:即在一個窗口(window)的生命週期內,窗口載入的全部頁面都是共享一個window.name的,每一個頁面對window.name都有讀寫的權限,window.name是持久存在一個窗口載入過的全部頁面中的,並不會因新頁面的載入而進行重置。注意,window.name的值只能是字符串的形式,且這個字符串的大小最大能允許2M左右甚至更大的容量,因瀏覽器而異,但通常是夠用的。

        example 1:

  如今在一個瀏覽器的一個標籤頁裏打開http://www.knightboy.cn/a.html頁面,你經過location.href = http://baidu.com/b.html,在同一個瀏覽器標籤頁裏打開了不一樣域名下的頁面。這時候這兩個頁面你可使用window.name來傳遞參數。由於window.name指的是瀏覽器窗口的名字,只要瀏覽器窗口相同,那麼不管在哪一個頁面裏訪問都是同樣的。

        example 2:

  你的http://www.knightboy.cn/a.html頁面裏使用<iframe>調用另外一個http://baidu.com/b.html頁面。這時候你想在a頁面裏獲取b頁面裏的DOM,而後進行操做。結果會發現不能得到b中的DOM。一樣會由於不一樣源而報錯,和上面提到的不一樣之處就是兩個頁面的一級域名也不相同。這時候document.domain就解決不了了。

        瀏覽器窗口有window.name屬性。這個屬性的最大特色就是,不管是否同源,只要在同一個窗口裏,前一個網頁設置了這個屬性,後一個網頁能夠讀取它。好比當在b頁面裏設定window.name="hello",你再返回到a頁面,在a頁面訪問window.name,能夠獲得hello。這種方法的優勢是,window.name容量很大,能夠放置很是長的字符串;缺點是必須監聽子窗口window.name屬性的變化,影響網頁性能。

<!--a.html-->
<DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>跨域</title>
    <script>
        function getData() {//iframe載入後執行該函數
            var iframe = document.getElementById("proxy");
            iframe.onload=function() {//a.html與iframe屬於同源了,能夠互相訪問
                var data = iframe.contentWindow.name;//獲取iframe裏的window.name,也就是b.html頁面給它設置的數據
                alert(data);
        }
         iframe.src="data.html";//這裏的data.html爲隨便的一個頁面,目的是,使得a.html能訪問到iframe裏的內容,也可設置成about:blank
    </script>    
<head>
<body>
    <iframe id="proxy" src="http://baidu.com/b.html" style="display:none" onload="getData()"></iframe>
</body>
</html>
<!--b.html-->
<script>
    window.name="this is some data you got from b.html";
<script>

6、window.postMessage方法跨域

   window.postMessage是一個安全的跨源通訊方法。通常狀況下,當且僅當執行腳本的頁面使用相同的協議(一般都是http)、相同的端口(http默認80,https默認443)和相同的host(兩個頁面的document.domain的值相同)時,才容許不一樣頁面上的腳本互相訪問。window.postMessage提供了一個可控的機制來安全地繞過這一限制,當其在正確使用的狀況下。window.postMessage解決的不是瀏覽器與服務器之間的交互,解決的是瀏覽器不一樣窗口之間的通訊問題,能夠作的就是同步兩個網頁,固然這兩個網頁須要屬於同一個基礎域名。

  example 1:

  在a頁面打開了一個不一樣源的b頁面,你想要讓a和b這兩個頁面互相通訊,例如,a要訪問b的LocalStorage。又或者,a頁面裏的iframe的src是不一樣源的b頁面,你想要讓a和b兩個頁面互相通訊,好比依舊是想經過a訪問b的LocalStorage。

  此時的解決辦法是:利用HTML5中新增的跨文檔通訊API,這個API爲window對象新增了一個window.postMessage方法,容許跨窗口通訊,不論這兩個窗口是否同源。a就能夠把它的Local Storage發給b,反之,依然可行。

  window.postMessage(message, targetOrigin, [transfer])三個參數分別表示爲:

  • message是向目標窗口發送的數據;
  • targetOrigin屬性來指定哪些窗口能收到消息事件,其值能夠是字符串「*」(表示無限制)或者一個URI(或者說是發送消息的目標域名);
  • transfer可選參數,是一串和message同時傳遞的Transferable對象,這些對象的全部權將被轉移給消息的接收方,而發送一方將再也不保有全部權。

  另外就是,消息的接收方必須有監聽事件,不然發送消息時就會報錯。

  window.addEventListener("message", onmessage); onmessage接收到的message事件包含三個屬性:

  data: 從其餘window中傳遞過來的數據。

  origin: 調用postMessage時消息發送窗口的origin。這個origin不能保證是該窗口的當前或將來origin,由於postMessage被調用後可能被導航到不一樣的位置。

  source: 對發送消息的窗口對象的引用;您可使用此來在具備不一樣origin的兩個窗口間創建雙向通訊。

//在a頁面執行
var
popUp = window.open('http://localhost:3000', 'title'); popUp.postMessage('Hello World!', 'http://localhost:3000');

  同時在http://localhost:3000的頁面裏監聽message事件:

window.onload = function() {
    window.addEventListener('message', onmessage);
}
function onmessage(event) {
    if(event.origin == 'http://localhost:8080') {//"發送方a的域名"
        console.log(event.data);//"Hello World!"
    } else {
      console.log(event.data);//"Hello World!"
}
}

咱們來看另一個例子:

//發送端代碼
var domain = "https://localhost",
    index = 1,
    target = window.open(`${domain}/postmesssage-target.html`);
function send()  {
    setInterval(() => {
        target.postMessage(`第${index++}次數據發送`, domain);
    }, 1000);
}
window.addEventListener("message", (e)=>{
    if(e.data === 'ok')
        send();
    else
        console.log(e.data);
});

//接收端代碼
<head>
    <script>
        opener.postMessage("ok", opener.domain);
    </script>
</head>
<body>
<p id = "test"></p>
<script>
    var test = document.querySelector("#test");
    window.addEventListener("message", (e)=>{
        if(e.origin !== "http://localhost") {
            return void 0;
        }
        test.innerText = e.data;
    });
</script> 
</body>

  上面頁面中,接受頁面已經加載了,這時發送一個消息給發送端,發送端再開始向接收端發送數據。

7、片斷識別符實現跨域

  片斷識別符就是指URL的#號後面的部分。好比,http://example.com/x.html#fragment的#fragment。若是隻是改變片斷標識符,頁面不會重複刷新。父窗口和iframe的子窗口之間的通信或者是window.open打開的子窗口之間的通信。

  父窗口能夠把信息,寫入子窗口的片斷標識符。

var src= originURL + '#' + data;
document.getElementById('myIFrame').src = src;

  子窗口經過監聽hashchange事件獲得通知。

window.onhashchange = checkMessage;
function checkMessage() {
    var message = window.location.hash;
    //...  
}

  一樣,子窗口也能夠改變父窗口的片斷標識符。

parent.location.href = target + '#' + hash;

  總之,父窗口改變子窗口的url的#號後面的部分,後者把要傳遞的的參數寫在#後面,子窗口監聽window.onhashchange事件,獲得通知,讀取window.location.hash解析出有用的數據。同時子窗口也能夠向父窗口傳遞數據。

 

參考:

跨域同源政策及其規避方法

跨域解決方案大全

跨域問題彙總

相關文章
相關標籤/搜索