跨域知識點部分總結

翊溪發表於2019-03-01

跨域的限制

  • 無法獲取cookielocalStorageindexDB
  • 無法獲得DOM
  • 無法傳送Ajax

關於cookie

共享cookie

cookie,是伺服器端寫入瀏覽器端的小段資訊,只有同源的網頁才能共享cookie一級域名相同但二級域名不同的網頁只要設定相同的document.domain,就可以共享cookie

補充知識點:

  • 一級域名 qq.com
  • 二級域名 game.qq.comwww.qq.comlol.qq.com
  • 三級域名 lpl.lol.qq.com

上述一段描述來自網上的文章,在此經過測試,提出了部分意見,關於意見是什麼,後面會提到,現在先證明上述描述的不精確性。

驗證的伺服器nginx配置:

server {
    listen      8080;
    server_name  w1.gaopeng.com;
    location / {
        root   /Users/gaopeng/Sites/w1.gaopeng.com;
        index  index.html index.htm;
    }
}

server {
    listen      8080;
    server_name  w2.gaopeng.com;
    location / {
        root   /Users/gaopeng/Sites/w2.gaopeng.com;
        index  index.html index.htm;
    }
}
複製程式碼

w1.gaopeng.comw2.gaopeng.com資料夾下的index.html內容

<script>
    document.domain = "gaopeng.com";
</script>
複製程式碼

主要目的是設定相同的document.cookie

瀏覽器開啟w1.gaopeng.com,發現document.domain已經被設定gaopeng.com,此時增加一個cookie

跨域知識點部分總結

瀏覽器再開啟w2.gaopeng.com
發現document.domain已經被設定gaopeng.com,但依舊無法獲取上述設定的cookiegao=peng

跨域知識點部分總結

此時我們看下cookie存在的表現:

跨域知識點部分總結

我們發現在w1.gaopeng.com設定的cookieDomain=w1.gaopeng.com

跨域知識點部分總結

發現w2.gaopeng.com依然沒有上述設定的cookie


突然想了一下如果將cookie設定為二級域名會怎麼樣呢?
w1.gaopeng.com下,設定cookie

跨域知識點部分總結

此時會發現在w2.gaopeng.com下,會獲取到剛剛設定的cookie

跨域知識點部分總結

再次觀察w1.gaopeng.comw2.gaopeng.comcookie

跨域知識點部分總結
跨域知識點部分總結

發現第二次設定的cookie共享了

因此得出了結論,cookie的共享與網站的是否同源並無明顯的關係,能否共享應該看cookie自身的domain,如果domain相同,就可以共享

猜想:存在兩個站點不同源(協議、埠不同)

A站:https://www.example.com:3443/

B站:http://www.example.com:3445/

由於cookiedomain都是www.example.com,那麼這兩個頁面的cookie是可以共享的

關於獲取DOM

iframewindow.open

只有同源的網頁才能獲取DOM,一級域名相同但二級域名不同的網頁只要設定相同的document.domain,就獲取DOM

// w1.gaopeng.com/index.html
index01.html
<iframe id="myIFrame" src="http://w2.gaopeng.com:8080/"></iframe>
<script>
  // document.domain = `gaopeng.com`;
  // 如果去掉註釋就可以獲取到DOM
</script>


// w2.gaopeng.com/index.html
index02.html
<script>
    document.domain = "gaopeng.com";
</script>
複製程式碼
跨域知識點部分總結

通過window.open的例項:

跨域知識點部分總結

LocalStorage、IndexDB

利用上述方式的document.domain等特性都無法滿足他們的通訊,可以使用下面的介紹的通訊,方式,在文章最後會介紹localStorage進行完全不同源的網站之間的通訊。

非AJAX並且完全不同源的網站通訊

完全不同源的nginx配置

server {
    listen      8080;
    server_name  w2.gaopeng.com;
    location / {
        root   /Users/gaopeng/Sites/w2.gaopeng.com;
        index  index.html index.htm;
    }
}
server {
    listen      8092;
    server_name  w2.gaopeng1.com;
    location / {
        root   /Users/gaopeng/Sites/w2.gaopeng1.com;
        index  index.html index.htm;
    }
}
複製程式碼

在這裡由於iframewindow.open的機理類似,所以只進行某一個種方式的演示

片段識別符號(iframe)

片段識別符號是指URL後面的#號部分,如果只是改變片段識別符號,頁面不會重新重新整理。

父視窗可以把資訊,寫入子視窗的片段識別符號。

// w2.gaopeng1.com/index.html
index02.html
<button id=`button`>改變hash</button>
<iframe id="myIFrame" src="http://w2.gaopeng1.com:8092/"></iframe>
<script>
  var srcUrl  = "http://w2.gaopeng1.com:8092/#" + "gao=peng"
  document.getElementById(`button`).onclick = function () {
    document.getElementById(`myIFrame`).src = srcUrl;
  }
</script>

// w2.gaopeng1.com/index.html
demo03.html
<script>
  window.onhashchange = checkMessage;
  function checkMessage() {
    var message = window.location.hash;
    console.log(`message`, message);
  }
</script>
複製程式碼

如圖所示,當點選改變hash按鈕時,iframe頁面會檢測到hash的變化執行checkMessage方法

跨域知識點部分總結

window.name(iframe)

瀏覽器視窗有一個window.name,這個屬性的最大特點是,無論是否同源,只要在同一個視窗裡,前一個網頁設定了這個屬性,後一個網頁可以讀取它。

// w2.gaopeng1.com/index.html
index02.html
<iframe id="myIFrame" src="http://w2.gaopeng1.com:8092/"></iframe>
<script>
  console.log(window.name);
</script>

// w2.gaopeng1.com/index.html
demo03.html
<script>
  window.name = "gao=peng";
</script>
複製程式碼

如圖所示,主視窗活動了子視窗設定的window.name

跨域知識點部分總結

window.postMessage(window.open)

上述的兩種方法都是投機取巧的方式,在HTML5中正式引入了:跨文件通訊 API

// w2.gaopeng1.com/index.html
index02.html
<iframe id="myIFrame" src="http://w2.gaopeng1.com:8092/"></iframe>
<script>
  // 獲取子視窗的引用
  var win = document.getElementsByTagName(`iframe`)[0].contentWindow;
  var obj = { name: `Jack` };
  win.postMessage(JSON.stringify({key: `storage`, data: obj}), `http://w2.gaopeng1.com:8092/`);
</script>

// w2.gaopeng1.com/index.html
demo03.html
<script>
  window.onmessage = function(e) {
  if (e.origin !== `http://w2.gaopeng1.com:8092/`) return;
  var payload = JSON.parse(e.data);
  switch (payload.method) {
    case `set`:
      localStorage.setItem(payload.key, JSON.stringify(payload.data));
      break;
    case `get`:
      // 獲取父視窗的引用
      var parent = window.parent;
      var data = localStorage.getItem(payload.key);
      parent.postMessage(data, `http://w2.gaopeng.com:8080/`);
      break;
    case `remove`:
      localStorage.removeItem(payload.key);
      break;
  }
};
</script>
複製程式碼

AJAX的跨域通訊

解決方案:

  • JSONP
  • WebSocket
  • CORS

主要談論一下CORScookie的影響

跨域知識點部分總結
跨域知識點部分總結

如上面兩張圖對比所示,當發起Ajax請求時:

  1. ajax會自動帶上同源的cookie,不會帶上不同源的cookie
  2. 可以通過前端設定withCredentialstrue, 後端設定Header的方式來讓ajax自動帶上不同源的cookie,但是這個屬性對同源請求沒有任何影響
  3. 如果ajax請求設定withCredentialstrue,注意: Access-Control-Allow-Origin必須制定特定的URL,不能是*, 且需要加上Access-Control-Allow-Credentials
    前端程式碼:
// 客戶端也需要設定credentials: "include",不然服務端設定的cookie,也傳送不過來
fetch("http://localhost:3000/post_form3", { method: "post", body: formData, credentials: "include" }).then(function(response) {
    return response.json();
}).then(function(data) {
	console.log(data);
}).catch(function(e) {
    console.log(e);
});
複製程式碼

後端程式碼

app.all(`*`, function(req, res, next) { 
	// 如果想讓cookie傳送至前端,必須設定為localhost:4000,而不是*
	res.header("Access-Control-Allow-Origin", req.headers.origin);
	res.header("Access-Control-Max-Age", 60); // 預檢請求的有效期
	res.header("Access-Control-Allow-Credentials", true);
	res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
	res.header("Access-Control-Allow-Methods","PUT, POST, GET, DELETE, OPTIONS"); 
	res.header("Content-Type", "application/json;charset=utf-8");
	next(); 
});
複製程式碼

關於上述程式碼中res.header("Access-Control-Max-Age", 60); // 預檢請求的有效期,預檢請求可以參考這篇文章

參考文章

瀏覽器同源政策及其規避方法

Ajax不會自動帶上cookie

相關文章