參考:https://segmentfault.com/a/1190000011145364
- 簡單的跨域請求jsonp即可,復雜的cors谦絮,窗口之間JS跨域postMessage推正,開發(fā)環(huán)境下接口跨域用nginx反向代理或node中間件比較方便
同源策略
- 同源是指"協(xié)議+域名+端口"三者相同跨新,即便兩個不同的域名指向同一個ip地址芹枷,也非同源
- 如果缺少了同源策略嘉裤,瀏覽器很容易受到XSS莉测、CSFR等攻擊
- 同源策略限制:
- Cookie撇簿、LocalStorage 和 IndexDB 無法讀取
- DOM 和 Js對象無法獲得
- AJAX 請求不能發(fā)送
- 跨域限制訪問恢口,其實是瀏覽器的限制孝宗,其實請求有發(fā)出去,對方服務器也有響應耕肩,只不過是被瀏覽器的同源策略攔下來
跨域解決方法
1. jsonp跨域
- 利用script可以跨域的特性因妇,缺點是只能實現(xiàn)get請求
- 應用場景:為了減輕web服務器的負載,我們把js猿诸,css沙峻,圖片等靜態(tài)資源分離到另一臺獨立域名的服務器上,在html頁面中跨域請求資源两芳。
<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', // 請求方式為jsonp
jsonpCallback: "onBack", // 自定義回調(diào)函數(shù)名
data: {}
});
- jsonpCallback:這個參數(shù)用來指定上面那個參數(shù)對應的回調(diào)函數(shù)名,如果不指定去枷,jQuery會自動生成一個隨機的函數(shù)名
2. 跨域資源共享(CORS)
- cros支持所有形式的http請求
- 普通跨域:只需要服務器端設置Access-Control-Allow-Origin即可怖辆,前端無需設置
-
帶cookie請求的跨域(簡單請求):前端設置withCredentials屬性為true,服務器端響應必須攜帶Access-Control-Allow-Credentials:true的首部删顶,而且服務器的Access-Control-Allow-Origin不能設置為*竖螃,而必須設置允許跨域訪問的域名;
- 同時逗余,Cookie依然遵循同源政策特咆,只有用服務器域名設置的Cookie才會上傳,其他域名的Cookie并不會上傳录粱,且(跨源)原網(wǎng)頁代碼中的document.cookie也無法讀取服務器域名下的Cookie腻格。
- 非簡單請求
- 非簡單請求是那種對服務器有特殊要求的請求,比如請求方法是PUT或DELETE啥繁,或者Content-Type字段的類型是application/json菜职。
- 非簡單請求的CORS請求,會在正式通信之前旗闽,增加一次HTTP查詢請求酬核,稱為"預檢"請求(preflight)蜜另。
- 瀏覽器先詢問服務器,當前網(wǎng)頁所在的域名是否在服務器的許可名單之中嫡意,以及可以使用哪些HTTP動詞和頭信息字段举瑰。只有得到肯定答復,瀏覽器才會發(fā)出正式的XMLHttpRequest請求蔬螟,否則就報錯
- 簡單請求此迅,滿足:
-
- 請求方法是以下三種方法之一:
- HEAD
- GET
- POST
-
(2)HTTP的頭信息不超出以下幾種字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三個值application/x-www-form-urlencoded、multipart/form-data促煮、text/plain
-
3. nginx反向代理
- 原理: 同源策略是瀏覽器的安全策略邮屁,不是HTTP協(xié)議的一部分。服務器端調(diào)用HTTP接口只是使用HTTP協(xié)議菠齿,不會執(zhí)行JS腳本佑吝,不需要同源策略,也就不存在跨越問題
- 通過nginx配置一個代理服務器(域名與domain1相同绳匀,端口不同)做跳板機芋忿,反向代理訪問domain2接口,并且可以順便修改cookie中domain信息疾棵,方便當前域cookie寫入戈钢,實現(xiàn)跨域登錄
- 配置:
- 找到nginx的配置文件"nginx.conf"
4. WebSocket協(xié)議跨域
- WebSocket protocol是HTML5一種新的協(xié)議。它實現(xiàn)了瀏覽器與服務器全雙工通信是尔,同時允許跨域通訊殉了,是server push技術的一種很好的實現(xiàn)。
5. document.domain + iframe跨域
- 只限于主域相同拟枚,子域不同的跨域應用場景
- 兩個頁面都通過js強制設置document.domain為基礎主域薪铜,就實現(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),通過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屬性之一隔箍,它可用于解決以下方面的問題:
- 頁面和其打開的新窗口的數(shù)據(jù)傳遞
- 多窗口之間消息傳遞
- 頁面與嵌套的iframe消息傳遞
- 上面三個場景的跨域數(shù)據(jù)傳遞
- 用法:postMessage(data,origin)方法接受兩個參數(shù)
- data: html5規(guī)范支持任意基本類型或可復制的對象,但部分瀏覽器只支持字符串脚乡,所以傳參時最好用JSON.stringify()序列化蜒滩。
- origin: 協(xié)議+主機+端口號,也可以設置為"*"奶稠,表示可以傳遞給任意窗口俯艰,如果要指定和當前窗口同源的話設置為"/"。
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返回數(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對象有一個name屬性窒典,該屬性有一個特征:即在一個窗口的生命周期內(nèi)蟆炊,窗口載入的所有的頁面都是共享一個window.name的,每一個頁面對window.name都有讀寫的權限瀑志,window.name是持久的存在于一個窗口載入的所有頁面中的涩搓,并不會因為新的頁面的載入而被重置污秆。