跨域的限制
- 無法獲取
cookie
、localStorage
、indexDB
- 無法獲得
DOM
- 無法傳送
Ajax
關於cookie
共享cookie
cookie
,是伺服器端寫入瀏覽器端的小段資訊,只有同源的網頁才能共享cookie
,一級域名相同但二級域名不同的網頁只要設定相同的document.domain
,就可以共享cookie
補充知識點:
- 一級域名
qq.com
- 二級域名
game.qq.com
、www.qq.com
、lol.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.com
、w2.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
,但依舊無法獲取上述設定的cookie
:gao=peng
此時我們看下cookie存在的表現:
我們發現在w1.gaopeng.com
設定的cookie
的Domain=w1.gaopeng.com
發現w2.gaopeng.com
依然沒有上述設定的cookie
突然想了一下如果將cookie設定為二級域名會怎麼樣呢?
在w1.gaopeng.com
下,設定cookie
此時會發現在w2.gaopeng.com
下,會獲取到剛剛設定的cookie
再次觀察w1.gaopeng.com
、w2.gaopeng.com
的cookie
發現第二次設定的cookie共享了
因此得出了結論,cookie
的共享與網站的是否同源並無明顯的關係,能否共享應該看cookie
自身的domain
,如果domain
相同,就可以共享
猜想:存在兩個站點不同源(協議、埠不同)
A站:https://www.example.com:3443/
B站:http://www.example.com:3445/
由於cookie
的domain
都是www.example.com
,那麼這兩個頁面的cookie
是可以共享的
關於獲取DOM
iframe
和window.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;
}
}
複製程式碼
在這裡由於iframe
與window.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
主要談論一下CORS
對cookie
的影響
如上面兩張圖對比所示,當發起Ajax請求時:
ajax
會自動帶上同源的cookie
,不會帶上不同源的cookie
- 可以通過前端設定
withCredentials
為true
, 後端設定Header
的方式來讓ajax
自動帶上不同源的cookie
,但是這個屬性對同源請求沒有任何影響 - 如果
ajax
請求設定withCredentials
為true
,注意: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); // 預檢請求的有效期
,預檢請求可以參考這篇文章