前端跨域問題總結

前端跨域問題的起因是什么—同源政策

1995年躺枕,同源政策由 Netscape 公司引入瀏覽器。目前供填,所有瀏覽器都實行這個政策拐云。
最初,它的含義是指近她,A網(wǎng)頁設置的 Cookie叉瘩,B網(wǎng)頁不能打開,除非這兩個網(wǎng)頁"同源"粘捎。所謂"同源"指的是"三個相同"薇缅。協(xié)議相同、域名相同攒磨、端口相同泳桦。

舉例來說
http://shaocx.com/index.html 這個網(wǎng)址協(xié)議是http://,域名是www.example.com娩缰,端口是80(默認端口可以省略)蓬痒。
它的同源情況如下。
http://shaocx.com/index2.html 同源
https://shaocx.com/index2.html 不同源(協(xié)議不同)
http://www.shaocx.com/index.html 不同源(域名不同)
http://shaocx.com:81/index.html 不同源(端口不同)

同源政策的目的

同源策略有助于保護使用經(jīng)過驗證會話的網(wǎng)站。下面是一個如果沒有同源政策會出現(xiàn)的例子梧奢。

假設用戶正在訪問一個銀行網(wǎng)站并且沒有注銷。然后用戶打開了一個有惡意JavaScript代碼的網(wǎng)站演痒,該網(wǎng)站向銀行網(wǎng)站請求數(shù)據(jù)亲轨。由于用戶沒有注銷銀行網(wǎng)站上的賬號,惡意代碼在銀行網(wǎng)站上做任意事情鸟顺。例如惦蚊,它可以獲取用戶最近一次交易的列表,或者創(chuàng)建一個新交易等讯嫂。

這主要是因為瀏覽器會根據(jù)請求的域名蹦锋,自動加上之前存儲在該域名底下的cookie。

而無意間訪問惡意網(wǎng)站的用戶欧芽,會期望他或她訪問的網(wǎng)站無法訪問銀行會話cookie莉掂。盡管JavaScript沒有直接訪問銀行會話cookie,但它仍然可以通過銀行網(wǎng)站的會話cookie向銀行網(wǎng)站發(fā)送和接收請求千扔。由于腳本基本上可以和用戶做的一樣憎妙,所以銀行網(wǎng)站的CSRF保護也不會有效。

由此可見曲楚,"同源政策"是必需的厘唾,否則 Cookie 可以共享,互聯(lián)網(wǎng)就毫無安全可言了龙誊。

同源政策限制范圍

1抚垃、 Cookie、LocalStorage 和 IndexDB 無法讀取趟大。
2鹤树、 DOM 無法獲得。
3护昧、 AJAX 請求不能發(fā)送魂迄。
4、 window對象無法獲取惋耙。

Cookie

Cookie 是服務器寫入瀏覽器的一小段信息捣炬,只有同源的網(wǎng)頁才能共享。但是绽榛,當兩個網(wǎng)頁一級域名相同湿酸,只是二級域名不同,瀏覽器允許通過設置document.domain共享 Cookie灭美。

1推溃、前端自己存取cookie時
A網(wǎng)頁是http://w1.shaocx.com/a.html
B網(wǎng)頁是http://w2.shaocx.com/b.html
document.cookie = "try1=1;domain=shaocx.com";
這樣cookie被設在頂級域名 .shaocx.com 上,兩個頁面都可以讀取到届腐。

2铁坎、后端添加cookie時
服務器也可以在設置Cookie的時候蜂奸,指定Cookie的所屬域名為一級域名,比如.shaocx.com硬萍。
Set-Cookie: key=value; domain=.shaocx.com; path=/

這里有一些地方需要注意:
1扩所、掛載在頂級域名上的是這種格式 .a.com ,前端在設置朴乖、書寫的時候祖屏,前面的 . 是不需要書寫的。但是在后端加上的時候买羞,這個 . 又是必須的袁勺。
2、cookie在讀取的時候畜普,只能讀取到key和value期丰。(path、host漠嵌、expires不能讀雀拦)
3、cookie在讀取時儒鹿,如果你在多個域名下都設置了化撕,那么會出現(xiàn)多個cookie。
4约炎、多個cookie時讀取的值會根據(jù)你使用的方法有所不同植阴。(我這邊的Cookies使用了js-cookie.js
document.cookie會讀取到所有可以讀取到的cookie。而且是根據(jù)先后插入的順序進行讀取圾浅。
Cookies.get()會讀取先插入的值(根據(jù)document.cookie的順序)掠手。
Cookies.getJSON()會優(yōu)先讀取后插入的值(轉換成對象后值被覆蓋)。

iframe

iframe窗口和window.open方法打開的窗口一樣狸捕,如果不同源喷鸽,它們與父窗口無法通信。

先來回顧一下iframe之間互相通信的方法灸拍。
父子iframe互相操作
子iframe獲取父iframe做祝,parent.window,top.window
父iframe獲取子iframe鸡岗,document.getElementById("a").contentWindow

跨域會存在以下問題
window對象無法讀取具體的對象值混槐,只能讀取系統(tǒng)默認方法。
window.location 可以設置轩性,但不能讀取声登。其它的 location 屬性和方法被禁止訪問;
document 不可以設置,也不能讀取悯嗓。
<iframe> 的 src 可以設置件舵,也可以讀取。但是在iframe中將自己的地址改變之后绅作,iframe的src地址并不會變更芦圾。

iframe - document.domain

如果兩個窗口根域名相同,那么設置上面介紹的document.domain屬性俄认,就可以規(guī)避同源政策,互相操作洪乍。

iframe - location.hash

location.hash是網(wǎng)頁中#后面的部分眯杏,如果只是改變hash值,頁面不會重新刷新壳澳。

// 父頁面可以把信息寫入已知地址的iframe的hash中岂贩。
document.getElementById('a').src = url + '#' + data;

// iframe中通過監(jiān)聽hashchange事件收到信息。
window.onhashchange = function () {
  var message = window.location.hash;
}

// iframe改變父頁面的hash地址
parent.location.href = url + "#" + data;
iframe - window.name

window一個name屬性巷波,它最大特點是萎津,無論是否同源都可以讀取它。
父窗口先打開一個子窗口抹镊,載入一個不同源的網(wǎng)頁锉屈,該網(wǎng)頁將信息寫入window.name屬性洲押。
window.name = data;
接著宪拥,子窗口跳回一個與主窗口同域的網(wǎng)址缸废。
location = ‘http://parent.url.com/xxx.html';
然后馆里,主窗口就可以讀取子窗口的window.name了娃兽。
var data = document.getElementById('myFrame').contentWindow.name;
這種方法的優(yōu)點是吭历,window.name容量很大墨辛,可以放置非常長的字符串英融;缺點是必須監(jiān)聽子窗口window.name屬性的變化铃彰,影響網(wǎng)頁性能绍豁。

// iframe頁面
window.name = 'I was there!';    // 這里是要傳輸?shù)臄?shù)據(jù),大小一般為2M牙捉,IE和firefox下可以大至32M左右
                                 // 數(shù)據(jù)格式可以自定義竹揍,如json、字符串

// 父頁面
var state = 0, 
iframe = document.createElement('iframe'),
loadfn = function() {
    if (state === 1) {
        var data = iframe.contentWindow.name;    // 讀取數(shù)據(jù)
        alert(data);    //彈出'I was there!'
    } else if (state === 0) {
        state = 1;
        iframe.contentWindow.location = "http://a.com/proxy.html";    // 設置的代理文件
    }  
};
iframe.src = 'http://b.com/data.html';
iframe.onload  = loadfn;
document.body.appendChild(iframe);
iframe - postMessage

上面兩種方法都屬于破解鹃共,HTML5為了解決這個問題鬼佣,引入了一個全新的API:跨文檔通信 API(Cross-document messaging)。
這個API為window對象新增了一個window.postMessage方法霜浴,允許跨窗口通信晶衷,不論這兩個窗口是否同源。這個方法可用在與window.open打開的新頁面進行通訊,或者與iframe中的頁面進行通訊晌纫。

// 這是iframe向父頁面窗口發(fā)送消息税迷。
window.parent.postMessage('Hello World!', 'http://bbb.com');
// 這是父頁面向iframe窗口發(fā)送消息。
document.getElementById("a").contentWindow.postMessage('Nice to see you', 'http://aaa.com');

postMessage方法的第一個參數(shù)是string格式的具體的信息內容锹漱,第二個參數(shù)是接收消息的窗口的源(origin)箭养,即"協(xié)議 + 域名 + 端口",設置該值后哥牍,只會向固定域名的頁面發(fā)送信息毕泌,使用其他域名窗口打開當前iframe時,不會發(fā)送消息嗅辣。也可以設為*撼泛,表示不限制域名,向所有窗口發(fā)送澡谭。
父窗口和子窗口都可以通過message事件愿题,監(jiān)聽對方的消息。

window.addEventListener('message', function(e) {
  // data是數(shù)據(jù)蛙奖,origin是域名潘酗。這邊最好也判斷一下,可以提升安全性
  console.log(e.data, e.origin);
});

localStorage

document.domain這種方法只適用于 Cookie 和 iframe 窗口雁仲,LocalStorage 無法通過這種方法仔夺,而要使用PostMessage API。

現(xiàn)有方案大多為使用postMessage與iframe組合來傳遞localStorage伯顶。
父頁面監(jiān)聽子頁面?zhèn)鱽淼膁ata囚灼,然后渲染到localStorage中。再子頁面監(jiān)聽父頁面?zhèn)鱽淼膁ata祭衩,渲染到自己的localStorage中灶体。

// 這是父頁面發(fā)送消息的代碼
var data = JSON.stringify({key: 'storage', data: {name: ‘Jack’}});
document.getElementsByTagName('iframe')[0].contentWindow.postMessage(data, 'http://bbb.com');

// 這是子頁面監(jiān)聽父頁面發(fā)來的消息,并放置在localStorage中掐暮。
window.onmessage = function(e) {
  var payload = JSON.parse(e.data);
  localStorage.setItem(payload.key, JSON.stringify(payload.data));
};

子頁面發(fā)送至父頁面也同理蝎抽。

AJAX

AJAX - 架設服務器代理

架設服務器代理(瀏覽器請求同源服務器,再由后者請求外部服務)
比如nginx的反向代理可以將請求直接轉發(fā)出去路克,并且接收返回值樟结。

AJAX - JSONP

JSONP是服務器與客戶端跨源通信的常用方法。最大特點就是簡單適用精算,老式瀏覽器全部支持瓢宦,服務器改造非常小。
它的基本思想是灰羽,網(wǎng)頁通過添加一個<script>元素驮履,向服務器請求JSON數(shù)據(jù)鱼辙,這種做法不受同源政策限制;服務器收到請求后玫镐,將數(shù)據(jù)放在一個指定名字的回調函數(shù)里傳回來倒戏。
首先,網(wǎng)頁動態(tài)插入<script>元素恐似,由它向跨源網(wǎng)址發(fā)出請求杜跷。

function addScriptTag(src) {
  var script = document.createElement('script');
  script.setAttribute("type","text/javascript");
  script.src = src;
  document.body.appendChild(script);
}
window.onload = function () {
  addScriptTag('http://a.com/ip?callback=foo');
}
function foo(data) {
  console.log('Your public IP address is: ' + data.ip);
};

上面代碼通過動態(tài)添加<script>元素,向服務器a.com發(fā)出請求矫夷。注意葛闷,該請求的查詢字符串有一個callback參數(shù),用來指定回調函數(shù)的名字双藕,這對于JSONP是必需的孵运。服務器收到這個請求以后,會將數(shù)據(jù)放在回調函數(shù)的參數(shù)位置返回蔓彩。

// 服務器
foo({"ip": "8.8.8.8"});

由于<script>元素請求的腳本,直接作為代碼運行驳概。這時赤嚼,只要瀏覽器定義了foo函數(shù),該函數(shù)就會立即調用顺又。

AJAX - WebSocket

WebSocket 協(xié)議在2008年誕生更卒,2011年成為國際標準。所有瀏覽器都已經(jīng)支持了稚照。
它的最大特點就是蹂空,服務器可以主動向客戶端推送信息,客戶端也可以主動向服務器發(fā)送信息果录,是真正的雙向平等對話上枕,屬于服務器推送技術的一種。

但是嚴格地說弱恒,WebSocket技術不屬于HTML5辨萍,這個技術是對HTTP無狀態(tài)連接的一種革新,本質就是一種持久性socket連接返弹,在瀏覽器客戶端通過javascript進行初始化連接后锈玉,就可以監(jiān)聽相關的事件和調用socket方法來對服務器的消息進行讀寫操作。與Ajax相比义起,Ajax技術需要客戶端發(fā)起請求拉背,而WebSocket服務器和客戶端可以彼此相互推送信息;XHR受到域的限制默终,而WebSocket允許跨域通信椅棺,這個特性導致我們至少可以用來做遠控犁罩。

WebSocket實現(xiàn)了全雙工通信,使WEB上的真正的實時通信成為可能土陪。瀏覽器和服務器只需要做一個握手的動作昼汗,然后,瀏覽器和服務器之間就形成了一條快速通道鬼雀。兩者之間就直接可以數(shù)據(jù)互相傳送顷窒。在WebSocket協(xié)議中,為我們實現(xiàn)即時服務帶來了三個好處:
1源哩、客戶端和服務器端之間數(shù)據(jù)傳輸時請求頭信息比較小,大概2個字節(jié)鞋吉。
2、服務器和客戶端可以相互主動的發(fā)送數(shù)據(jù)給對方励烦。
3谓着、不需要多次創(chuàng)建TCP請求和銷毀,節(jié)約寬帶和服務器的資源坛掠。

具體可以參考
WebSocket 教程 - 阮一峰
跨域(二)——WebSocket

AJAX - CORS

CORS是跨源資源分享(Cross-Origin Resource Sharing)的縮寫赊锚。它是W3C標準,是跨源AJAX請求的根本解決方法屉栓。相比JSONP只能發(fā)GET請求舷蒲,CORS允許任何類型的請求。
CORS需要瀏覽器和服務器同時支持友多。目前牲平,所有瀏覽器都支持該功能,IE瀏覽器不能低于IE10域滥。
整個CORS通信過程纵柿,都是瀏覽器自動完成,不需要用戶參與启绰。對于開發(fā)者來說昂儒,CORS通信與同源的AJAX通信沒有差別,代碼完全一樣酬土。瀏覽器一旦發(fā)現(xiàn)AJAX請求跨源荆忍,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求撤缴,但用戶不會有感覺刹枉。
因此,實現(xiàn)CORS通信的關鍵是服務器屈呕。只要服務器實現(xiàn)了CORS接口微宝,就可以跨源通信。

具體可以參考 跨域資源共享 CORS 詳解 - 阮一峰

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末虎眨,一起剝皮案震驚了整個濱河市蟋软,隨后出現(xiàn)的幾起案子镶摘,更是在濱河造成了極大的恐慌,老刑警劉巖岳守,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凄敢,死亡現(xiàn)場離奇詭異,居然都是意外死亡湿痢,警方通過查閱死者的電腦和手機涝缝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來譬重,“玉大人拒逮,你說我怎么就攤上這事⊥喂妫” “怎么了滩援?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長塔嬉。 經(jīng)常有香客問我玩徊,道長,這世上最難降的妖魔是什么谨究? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任佣赖,我火速辦了婚禮,結果婚禮上记盒,老公的妹妹穿的比我還像新娘。我一直安慰自己外傅,他們只是感情好纪吮,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著萎胰,像睡著了一般碾盟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上技竟,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天冰肴,我揣著相機與錄音,去河邊找鬼榔组。 笑死熙尉,一個胖子當著我的面吹牛,可吹牛的內容都是我干的搓扯。 我是一名探鬼主播检痰,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼锨推!你這毒婦竟也來了铅歼?” 一聲冷哼從身側響起公壤,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎椎椰,沒想到半個月后厦幅,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡慨飘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年确憨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片套媚。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡缚态,死狀恐怖,靈堂內的尸體忽然破棺而出堤瘤,到底是詐尸還是另有隱情玫芦,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布本辐,位于F島的核電站桥帆,受9級特大地震影響,放射性物質發(fā)生泄漏慎皱。R本人自食惡果不足惜老虫,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望茫多。 院中可真熱鬧祈匙,春花似錦、人聲如沸天揖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽今膊。三九已至些阅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間斑唬,已是汗流浹背市埋。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留恕刘,地道東北人缤谎。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像褐着,于是被迫代替她去往敵國和親弓千。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內容

  • 前端開發(fā)中镣陕,跨域使我們經(jīng)常遇到的一個問題,也是面試中經(jīng)常被問到的一些問題姻政,所以呆抑,這里,我們做個總結汁展。小小問題鹊碍,不足...
    Nealyang閱讀 470評論 0 0
  • 什么是跨域 跨域,是指瀏覽器不能執(zhí)行其他網(wǎng)站的腳本食绿。它是由瀏覽器的同源策略造成的侈咕,是瀏覽器對JavaScript實...
    Yaoxue9閱讀 1,286評論 0 6
  • 什么是跨域 跨域,是指瀏覽器不能執(zhí)行其他網(wǎng)站的腳本器紧。它是由瀏覽器的同源策略造成的耀销,是瀏覽器對JavaScript實...
    HeroXin閱讀 831評論 0 4
  • 什么是跨域 跨域,是指瀏覽器不能執(zhí)行其他網(wǎng)站的腳本铲汪。它是由瀏覽器的同源策略造成的熊尉,是瀏覽器對JavaScript實...
    他方l閱讀 1,059評論 0 2
  • 寫筆記的頁面和印象筆記如出一轍。 思路是大大改變了筆記的共享性和公開度掌腰。 有些像印象筆記和博客的結合體狰住。
    慵懶的不倒翁閱讀 295評論 0 0