參考:https://segmentfault.com/a/1190000011145364
- 簡(jiǎn)單的跨域請(qǐng)求jsonp即可,復(fù)雜的cors,窗口之間JS跨域postMessage,開(kāi)發(fā)環(huán)境下接口跨域用nginx反向代理或node中間件比較方便
同源策略
- 同源是指"協(xié)議+域名+端口"三者相同,即便兩個(gè)不同的域名指向同一個(gè)ip地址,也非同源
- 如果缺少了同源策略,瀏覽器很容易受到XSS、CSFR等攻擊
- 同源策略限制:
- Cookie、LocalStorage 和 IndexDB 無(wú)法讀取
- DOM 和 Js對(duì)象無(wú)法獲得
- AJAX 請(qǐng)求不能發(fā)送
- 跨域限制訪問(wèn),其實(shí)是瀏覽器的限制,其實(shí)請(qǐng)求有發(fā)出去,對(duì)方服務(wù)器也有響應(yīng),只不過(guò)是被瀏覽器的同源策略攔下來(lái)
跨域解決方法
1. jsonp跨域
- 利用script可以跨域的特性,缺點(diǎn)是只能實(shí)現(xiàn)get請(qǐng)求
- 應(yīng)用場(chǎng)景:為了減輕web服務(wù)器的負(fù)載,我們把js,css,圖片等靜態(tài)資源分離到另一臺(tái)獨(dú)立域名的服務(wù)器上,在html頁(yè)面中跨域請(qǐng)求資源。
<script>
var script=document.createElement("script");
script.type="text/javascript";
//傳參并指定回調(diào)函數(shù)為onblack
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=onBack';
document.head.append(script);
//回調(diào)函數(shù)
function onBack(res) {
alert(JSON.stringify(res));
}
</script>
onBack({"status": true, "user": "admin"})
$.ajax({
url: 'http://www.domain2.com:8080/login',
type: 'get',
dataType: 'jsonp', // 請(qǐng)求方式為jsonp
jsonpCallback: "onBack", // 自定義回調(diào)函數(shù)名
data: {}
});
- jsonpCallback:這個(gè)參數(shù)用來(lái)指定上面那個(gè)參數(shù)對(duì)應(yīng)的回調(diào)函數(shù)名,如果不指定,jQuery會(huì)自動(dòng)生成一個(gè)隨機(jī)的函數(shù)名
2. 跨域資源共享(CORS)
- cros支持所有形式的http請(qǐng)求
- 普通跨域:只需要服務(wù)器端設(shè)置Access-Control-Allow-Origin即可,前端無(wú)需設(shè)置
-
帶cookie請(qǐng)求的跨域(簡(jiǎn)單請(qǐng)求):前端設(shè)置withCredentials屬性為true,服務(wù)器端響應(yīng)必須攜帶Access-Control-Allow-Credentials:true的首部,而且服務(wù)器的Access-Control-Allow-Origin不能設(shè)置為*,而必須設(shè)置允許跨域訪問(wèn)的域名;
- 同時(shí),Cookie依然遵循同源政策,只有用服務(wù)器域名設(shè)置的Cookie才會(huì)上傳,其他域名的Cookie并不會(huì)上傳,且(跨源)原網(wǎng)頁(yè)代碼中的document.cookie也無(wú)法讀取服務(wù)器域名下的Cookie。
- 非簡(jiǎn)單請(qǐng)求
- 非簡(jiǎn)單請(qǐng)求是那種對(duì)服務(wù)器有特殊要求的請(qǐng)求,比如請(qǐng)求方法是PUT或DELETE,或者Content-Type字段的類(lèi)型是application/json。
- 非簡(jiǎn)單請(qǐng)求的CORS請(qǐng)求,會(huì)在正式通信之前,增加一次HTTP查詢(xún)請(qǐng)求,稱(chēng)為"預(yù)檢"請(qǐng)求(preflight)。
- 瀏覽器先詢(xún)問(wèn)服務(wù)器,當(dāng)前網(wǎng)頁(yè)所在的域名是否在服務(wù)器的許可名單之中,以及可以使用哪些HTTP動(dòng)詞和頭信息字段。只有得到肯定答復(fù),瀏覽器才會(huì)發(fā)出正式的XMLHttpRequest請(qǐng)求,否則就報(bào)錯(cuò)
- 簡(jiǎn)單請(qǐng)求,滿足:
-
- 請(qǐng)求方法是以下三種方法之一:
- HEAD
- GET
- POST
-
(2)HTTP的頭信息不超出以下幾種字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三個(gè)值application/x-www-form-urlencoded、multipart/form-data、text/plain
-
3. nginx反向代理
- 原理: 同源策略是瀏覽器的安全策略,不是HTTP協(xié)議的一部分。服務(wù)器端調(diào)用HTTP接口只是使用HTTP協(xié)議,不會(huì)執(zhí)行JS腳本,不需要同源策略,也就不存在跨越問(wèn)題
- 通過(guò)nginx配置一個(gè)代理服務(wù)器(域名與domain1相同,端口不同)做跳板機(jī),反向代理訪問(wèn)domain2接口,并且可以順便修改cookie中domain信息,方便當(dāng)前域cookie寫(xiě)入,實(shí)現(xiàn)跨域登錄
- 配置:
- 找到nginx的配置文件"nginx.conf"
4. WebSocket協(xié)議跨域
- WebSocket protocol是HTML5一種新的協(xié)議。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工通信,同時(shí)允許跨域通訊,是server push技術(shù)的一種很好的實(shí)現(xiàn)。
5. document.domain + iframe跨域
- 只限于主域相同,子域不同的跨域應(yīng)用場(chǎng)景
- 兩個(gè)頁(yè)面都通過(guò)js強(qiáng)制設(shè)置document.domain為基礎(chǔ)主域,就實(shí)現(xiàn)了同域
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
document.domain = 'domain.com';
var user = 'admin';
</script>
- 子窗口:(http://child.domain.com/b.html),通過(guò)window.parent獲取變量
<script>
document.domain = 'domain.com';
// 獲取父窗口中變量
alert('get js data from parent ---> ' + window.parent.user);
</script>
6. postMessage跨域
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是為數(shù)不多可以跨域操作的window屬性之一,它可用于解決以下方面的問(wèn)題:
- 頁(yè)面和其打開(kāi)的新窗口的數(shù)據(jù)傳遞
- 多窗口之間消息傳遞
- 頁(yè)面與嵌套的iframe消息傳遞
- 上面三個(gè)場(chǎng)景的跨域數(shù)據(jù)傳遞
- 用法:postMessage(data,origin)方法接受兩個(gè)參數(shù)
- data: html5規(guī)范支持任意基本類(lèi)型或可復(fù)制的對(duì)象,但部分瀏覽器只支持字符串,所以傳參時(shí)最好用JSON.stringify()序列化。
- origin: 協(xié)議+主機(jī)+端口號(hào),也可以設(shè)置為"*",表示可以傳遞給任意窗口,如果要指定和當(dāng)前窗口同源的話設(shè)置為"/"。
a.html:(http://www.domain1.com/a.html)
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
iframe.onload = function() {
var data = {
name: 'aym'
};
// 向domain2傳送跨域數(shù)據(jù)
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
};
// 接受domain2返回?cái)?shù)據(jù)
window.addEventListener('message', function(e) {
alert('data from domain2 ---> ' + e.data);
}, false);
</script>
b.html:(http://www.domain2.com/b.html)
<script>
// 接收domain1的數(shù)據(jù)
window.addEventListener('message', function(e) {
alert('data from domain1 ---> ' + e.data);
var data = JSON.parse(e.data);
if (data) {
data.number = 16;
// 處理后再發(fā)回domain1
window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
}
}, false);
</script>
7. window.name
- window對(duì)象有一個(gè)name屬性,該屬性有一個(gè)特征:即在一個(gè)窗口的生命周期內(nèi),窗口載入的所有的頁(yè)面都是共享一個(gè)window.name的,每一個(gè)頁(yè)面對(duì)window.name都有讀寫(xiě)的權(quán)限,window.name是持久的存在于一個(gè)窗口載入的所有頁(yè)面中的,并不會(huì)因?yàn)樾碌捻?yè)面的載入而被重置。