注:本文完整示例地址
先來講一個概念就是同源,同源指的是協議,端口,域名所有相同。javascript
同源策略(Same origin policy)是一種約定,它是瀏覽器最核心也最基本的安全功能,若是缺乏了同源策略,則瀏覽器的正常功能可能都會受到影響。能夠說Web是構建在同源策略基礎之上的,瀏覽器只是針對同源策略的一種實現。css
同源策略是處於對用戶安全的考慮,若是非同源就會受到如下限制:html
cookie不能讀取前端
dom沒法得到java
ajax請求不能發送git
可是事實是常常須要藉助非同源來提供數據,因此就須要進行跨域請求。github
JSONP
是指JSON Padding
,JSONP
是一種非官方跨域數據交換協議,因爲script
的src
屬性能夠跨域請求,因此JSONP
利用的就是瀏覽器的這個「漏洞」,須要通訊時,動態的插入一個script
標籤。請求的地址通常帶有一個callback
參數,假設須要請求的地址爲http://localhost:666?callback=show
,服務端返回的代碼通常是show(數據)
的JSON
數據,而show
函數偏偏是前臺須要用這個數據的函數。JSONP
很是的簡單易用,自動補全API利用的就是JSONP
,下面來看一個例子:web
// 前端請求代碼 function jsonp (callback) { var script = document.createElement("script"), url = `http://localhost:666?callback=${callback}`; script.setAttribute("src", url); document.querySelector("head").appendChild(script); } function show (data) { console.log(`學生姓名爲:${data.name},年齡爲:${data.age},性別爲${data.sex}`); } jsonp("show"); // 後端響應代碼 const student = { name: "zp1996", age: 20, 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
雖然說簡單易用,可是有一個很大問題,那就是JSONP
只能進行get
請求ajax
CORS(跨域資源共享)是由W3C制定的跨站資源分享標準,可讓AJAX
實現跨域訪問。想要了解跨域的話,首先須要瞭解下簡單請求:json
請求方式爲GET
或者POST
倘若請求是POST
的話,Content-Type
必須爲下列之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
不含有自定義頭(相似於segmentfault自定義的頭X-Hit
)
對於簡單請求的跨域只須要進行一次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("http://localhost:666?page=cors", { name: "zp1996", age: 20, 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 boy~`); } else { res.end("No!a bad boy~"); } });
打開控制檯觀察能夠發現,Network
只是發出了一次請求,可是對於非簡單請求來講,須要兩次http
請求,在真正的請求以前須要進行一次預請求,下圖是進行一次預請求的請求/響應:
觀察響應頭,能夠發現須要多出了兩個響應頭:
Access-Control-Allow-Headers
,用來指明在實際的請求中,可使用那些自定義的http
請求頭。
Access-Control-Max-Age
,用來指定這次預請求的結果的有效期,在有效期內則不會發出預請求,有點像緩存的感受。
固然還有諸如好多這樣的響應頭,請你們自行搜索瞭解,這裏就再也不過多介紹,下面來看下對於非簡單請求跨域的代碼處理:
// 前端請求代碼 ajaxPost("http://localhost:666?page=cors", { name: "zp1996", age: 20, sex: "male" }, { "X-author": "zp1996" }) .then((text) => { console.log(text); }, () => { console.log("請求失敗"); }); // 後端處理,補充在簡單請求代碼註釋處 if (req.method === "OPTIONS") { res.writeHead(200, { "Access-Control-Max-Age": 3000, "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "X-author", "Content-Type": "application/json;charset=utf-8" }); res.end(); return void 0; }
既然能夠利用script
的「漏洞」來進行JSONP
跨域,那麼是否是也能夠利用css
樣式寫能夠進行跨域請求來進行跨域呢?答案確定是yes,利用css
還有一個好處那就是,當被注入攻擊腳本時,css
儘管被注入,也不會引發什麼大的安全問題,頂多也就是把頁面的樣式給改變,而js
被注入的話,cookie
就有可能被盜取等一系列安全問題出現。大牛已經將其作的很是完善,你們能夠去star王集鵠(zswang)CSST,這裏我就把我所理解給你們簡單的分享下:
// 前端代碼 const id = "csst", ele = document.querySelector(`#${id}`), head = document.querySelector("head"); function getStyle (ele, prop) { return getComputedStyle(ele, "").getPropertyValue(prop); } function loadCss (url) { return new Promise((resolve) => { const link = document.createElement("link"); link.setAttribute("rel", "stylesheet"); link.setAttribute("type", "text/css"); link.setAttribute("href", url); ele.addEventListener("webkitAnimationStart", function () { resolve(getStyle(ele, "content")); }); head.appendChild(link); }); } loadCss(`http://localhost:666?page=data.css&id=${id}`).then((data) => { console.log(data); }); // 後端代碼 function cssData (id) { return ` @keyframes a{ from{ } to{ color: red; } } #${id} { content: "這種是很好,可是隻能傳輸文本啊"; animation: a 2s; } `; } res.writeHead(200, { "Content-Type": "text/css" }); res.end(cssData(query.id));
經過代碼能夠看出這種實現方式是靠元素的content
來拿接收到的數據,因此傳輸的只能是文本。至於爲何要返回動畫?是由於不利用動畫,沒法來對css
腳本加載進行監測,也就沒法進行回調(因爲谷歌/火狐不支持link
的onload
和onreadychange
,因此利用animationstart
事件)。
window.postMessage 是一個安全的跨源通訊的方法。通常狀況下,當且僅當執行腳本的頁面使用相同的協議(一般都是 http)、相同的端口(http默認使用80端口)和相同的 host(兩個頁面的 document.domain 的值相同)時,才容許不一樣頁面上的腳本互相訪問。 window.postMessage 提供了一個可控的機制來安全地繞過這一限制,當其在正確使用的狀況下。
window.postMessage
解決的不是瀏覽器與服務器之間的交互,解決的是瀏覽器不一樣的窗口之間的通訊問題,能夠作的就是同步兩個網頁,固然這兩個網頁應該是屬於同一個基礎域名。
// 發送端代碼 var domain = "http://localhost", index = 1, target = window.open(`${domain}/postmessage-target.html`); function send () { setInterval(() => { target.postMessage(`第${index++}次數據發送`, domain); }, 1000); } send(); // 接受端代碼 <div id="test">沒有數據過來啊<div> <script type="text/javascript"> var test = document.querySelector("#test"); window.addEventListener("message", e => { if (e.origin !== "http://localhost") { return void 0; } test.innerText = e.data; }); </script>
上述代碼實現了向一個頁面向另外一個發送數據,可是這麼寫每每有着一些「危險」,須要知道的是,postMessage
是向document
對象中,網絡鏈接有時會很慢,可能會出現些問題,因此最好的方式是接受頁面已經開始加載了,這時發送一個消息給發送端,發送端在開始向接收端發送數據。改進下:
// 發送端添加代碼 window.addEventListener("message", (e) => { if (e.data === "ok") send(); else console.log(e.data); }); // 接受端的head裏面加上script標籤 <script type="text/javascript"> opener.postMessage("ok", opener.domain); </script>
window.name 的美妙之處:name 值在不一樣的頁面(甚至不一樣域名)加載後依舊存在,而且能夠支持很是長的 name 值(2MB)
這個方式我基本上沒有用過,因此沒有過多的發言權,你們想了解這個技術的話,能夠經過懌飛(圓心):使用 window.name 解決跨域問題,圓心大神解釋的很是透徹。
將子域和主域的document.domain
設爲同一個主域
前提條件:
這兩個域名必須屬於同一個基礎域名
並且所用的協議,端口都要一致,不然沒法利用document.domain
進行跨域