跨域不徹底探究

跨域不徹底探究

前言

  首先歡迎你們關注個人Github博客,也算是對個人一點鼓勵,畢竟寫東西無法得到變現,能堅持下去也是靠的是本身的熱情和你們的鼓勵。最近工做上事情比較繁多致使博客一度斷更,爲了挽救慘淡的關注量併兼顧本身有限的時間,準備近期多推出一些有關前端的基礎知識學習,一塊兒夯實基礎。但願你們多多關注呀!
  javascript

引子  

  跨域問題在Web開發中一直都是一個很是常見的問題,但在平常工做說實話使用頻率並無那麼多,再加上重使用輕原理致使我對跨域問題理解的一直很是的淺顯,藉此機會讓咱們好好探索一下。前端

同源策略

  跨域問題的起源於衆所周知的瀏覽器同源策略。做爲現在Web安全基石的同源策略,早在上個世紀九十年代由網景公司提出,當時僅僅只是針對於Cookie的訪問,即不一樣源的網頁之間Cookie不能共享。後來同源策略變得更加嚴格,安全性也逐步提升,升級爲:java

  1. 不一樣源域 Cookie、LocalStorage 和 IndexDB 沒法讀取。
  2. 不一樣源域 DOM 沒法得到。
  3. 不一樣源域 AJAX 請求不能發送

  說了這麼多同源,那麼何爲同源。一般狀況下,若是一個源擁有相同的協議(protocol)、主機(host)、端口(port),則稱其爲同源。固然咱們說了一般狀況下,也就意味着這個方面有例外存在,IE毫無疑問的在這個方面承擔了它固有的職責。IE並無將端口號加入到同源策略的組成部分,所以http://host:81http://host:80是屬於同源的。雖然同源的安全限制是必要的,可是同時也帶了束縛,假設我確實須要向不一樣源的地址發送請求該怎麼辦呢?那就回到了咱們所要討論的跨域問題。git

跨域解決方案

JSONP

  往往提起跨域問題,第一個想到的就是JSONP了。JSONP與JSON雖然看起來很像,但卻有本質區別
。JSONP全稱是JSON with Padding,並非一種新的數據格式,而是數據格式JSON的一種「使用模式」。瀏覽器的同源限制對含有src屬性的元素(例如: scriptimg)不起做用,JSONP就是利用了script的這個特性。github

基本原理

  前面咱們說了JSONP利用的是script標籤,繞過瀏覽器的同源策略,試想咱們經過一個URL獲取JSON數據是可能的。然而JSON數據倒是不可執行的,所以JSONP經過將返回的JSON數據用函數包裹來實現執行的目的,這也就是JSONP中P表示padding(包裹)的含義。由於須要函數包裹數據,因此先後端須要提早約定好函數名,咱們通常會在請求的URL中帶上函數名稱做爲參數。例如請求接口:ajax

http://api.demo.com/data?userid=1&jsonp=parseResponse

  服務器會將對應的數據填充到函數parseResponse:express

parseResponse({"Name": "小明", "Id" : 1823, "Rank": 7})

概念實現

  咱們實現一個最簡單的函數用來建立一個JSONP請求:json

function createJSONPRequest(url, callbackName){
    var script = document.createElement("script");
    script.src = url +"?callback=handleResponse"; 
    document.body.appendChild(script);
}

function handleResponse(res){
    console.log(res);
}

createJSONPRequest("http://localhost:3001/jsonp", "parseResponse")

而後咱們用express.js來響應這個JSONP請求:後端

// 服務器端口號:3001
app.get('/jsonp', (req, res) => {
    var callbackName = req.query.callback;
    var mockData = {
      name: "MrErHu"
    };
    res.send(`${callbackName}(${JSON.stringify(mockData)})`);
})

運行時你會發現瀏覽器會正確打印出跨域訪問的數據,說明咱們的跨域請求成功。api

實踐

  固然咱們並不須要在前端手動去實現該函數,不少第三方庫已經封裝了JSONP的請求,咱們以JQuery提供的Ajax理由爲例,上面的請求用JQuery去實現代碼是:

// 客戶端端口號:3000
$.ajax({
    url: "http://localhost:3001/jsonp",
    type: "GET",
    dataType: "jsonp",
    jsonpCallback: "parseResponse",
    success: function (res) {
        console.log(res);
    }
});

  咱們能夠看到在JQuery中提供的ajax函數中經過配置dataTypejsonp,則能夠實現JSONP請求,須要注意的是JQuery只是將其封裝在ajax函數內,兩者實質上有本質區別。

  瞭解JSONP的實現原理,咱們知道這玩意確定不會存在瀏覽器兼容性問題,畢竟我不相信會存在不支持script標籤的瀏覽器。可是由於正是經過script標籤實現的,JSONP也就只能支持GET請求,那麼若是咱們想實現POST請求的跨域有什麼好的辦法嗎?

CORS

  CORS是Cross-origin resource sharing的縮寫,即跨域資源共享,屬於W3C標準,容許跨域發送XMLHttpRequest請求,支持多種HTTP Method。與JSONP的原理不一樣的是,CORS採用的是先後端HTTP表頭協商的方式(我本身起的名字)判斷是否容許跨域訪問,而且相比與JSONP來講,CORS須要客戶端和服務端共同支持,而且整個過程由瀏覽器自動完成。對於前端開發者而言,跨域的CORS通訊與普通的Ajax通訊沒有差異,整個跨域處理的過程主要集中在服務器端。CORS通訊分紅簡單請求(simple request)和非簡單請求(not-so-simple request)兩種模式。

簡單請求

  所謂的簡單請求是指,請求方法屬於: HEADGETPOST之一,而且HTTP的頭信息不超出如下幾種字段:

Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:僅限於三個值:application/x-www-form-urlencoded、multipart/form-data、text/plain

  若是不知足以上任一條件,均屬於非簡單請求。對於簡單請求,瀏覽器會直接發送CORS請求。當咱們使用Ajax發送一條跨域請求,瀏覽器會在HTTP請求頭(Request Headers)中增長Origin屬性:

  Origin屬性用來代表當前的請求來自於哪一個源(協議、主機、端口)。若是服務端支持CORS跨域,則須要在響應頭中返回如下屬性:

Access-Control-Allow-Origin: 
Access-Control-Allow-Credentials
Access-Control-Expose-Headers
  • Access-Control-Allow-Origin: 表示跨域的訪問的源,其中"*"表示容許來自任何源的請求跨域訪問
  • Access-Control-Allow-Credentials: 表示跨域訪問是否容許帶有Cookie信息,該值僅能夠容許設置爲true。若是服務器不容許請求攜帶Cookie,則不須要發送該屬性。值得注意的是,攜帶的Cookie信息僅僅只是所跨域的服務器域名設置的Cookie,不能攜帶其餘域名下的Cookie信息,Cookie仍然循序同源策略。而且還須要知足Access-Control-Allow-Origin指定的域與當前的請求的域徹底一致(不能是"*")以及在XMLHttpRequest顯式設置withCredentials屬性爲true,表示瀏覽器也容許發送Cookie。
  • Access-Control-Expose-Headers: 該字段可選,表示瀏覽器能夠從該XMLHttpRequest請求中拿到的響應頭信息。默認僅能拿到Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma這六個屬性,若是想取得其餘的屬性,必須在該屬性中指定。

咱們用express.js模擬一下:

// 服務器端口號:3001
app.post('/cors', (req, res) => {
    var mockData = {
        name: "MrErHu"
    };
    var origin = req.get("origin");
    res.set("Access-Control-Allow-Origin", origin);
    res.set("Access-Control-Allow-Credentials", true);
    res.send(JSON.stringify(mockData))
});

  當咱們前端Ajax請求:

// 瀏覽器端口號:3000
$.ajax({
    url: "http://localhost:3001/cors",
    type: "POST",
    success: function (res) {
        console.log(res);
    }
});

你會發現該條請求已經不會出現跨域問題,能夠正確訪問到數據。

非簡單請求

  對於非簡單請求,瀏覽器會預先發送一次預檢請求,詢問服務器是否容許跨域訪問以及是否容許HTTP方法,只有獲得確定答覆後,瀏覽器纔會發出正式的HTTP請求。好比咱們跨域向服務器發送一次PUT請求:

// 瀏覽器端口號:3000
$.ajax({
    url: "http://localhost:3001/cors",
    type: "PUT",
    success: function (res) {
        console.log(res);
    }
});

  你會發現瀏覽器首先會發送OPTIONS請求去預檢本次跨域請求。

  當預檢請求檢測了OriginAccess-Control-Request-MethodAccess-Control-Request-Headers屬性後就會正式發送請求。須要注意的是,不只僅是上述三個屬性,預檢請求還會返回Access-Control-Max-Age屬性用來表示該條預檢請求的有效期,在有效期內,瀏覽器不會再次發送預檢請求,僅會使用該條緩存的預檢請求。

  咱們用express.js來模擬本次請求:

// 服務器端口號:3001
app.options('/cors', (req, res) => {
    var origin = req.get("origin");
    res.set("Access-Control-Allow-Origin", origin);
    res.set("Access-Control-Allow-Credentials", true);
    res.set("Access-Control-Allow-Methods", "PUT");
    res.set("Access-Control-Max-Age", 24 * 60 * 60 * 1000);
    res.send();
});

app.put('/cors', (req, res) => {
    var mockData = {
        name: "MrErHu"
    };
    var origin = req.get("origin");
    res.set("Access-Control-Allow-Origin", origin);
    res.set("Access-Control-Allow-Credentials", true);
    res.send(JSON.stringify(mockData))
});

  你就會發現本次PUT跨域請求成功,正確返回數據,達到了咱們跨域的要求。

對比

  與JSONP相比,CORS支持更多的HTTP方法而且整個過程由瀏覽器自動完成,可是僅僅只兼容IE10及以上的瀏覽器,若是須要兼容老版本IE瀏覽器,CORS可能就不適合你了。
  

後言

咱們這邊文章着重講述了兩種跨域的基本原理,但實際上圍繞跨域問題還有不少值得學習的地方,好比後端對跨域請求的處理,以及前端跨域帶來種種的安全性問題。其實不只僅是JSONP與CORS,Websocket也能實現跨域請求,之後有機會能夠學習一下,最後仍是歡迎你們關注個人博客,水平欠佳,但願體諒,願一同進步。

相關文章
相關標籤/搜索