最新博客站點:歡迎來訪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])三個參數分別表示爲:
另外就是,消息的接收方必須有監聽事件,不然發送消息時就會報錯。
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解析出有用的數據。同時子窗口也能夠向父窗口傳遞數據。
參考: