瀏覽器跨域是一個前端很常見的問題擅这。
造成跨域的兩種策略瀏覽器的同源策略會導(dǎo)致跨域,這里同源策略又分為以下兩種DOM同源策略:
1.禁止對不同源頁面DOM進行操作采章。這里主要場景是iframe跨域的情況运嗜,不同域名的iframe是限制互相訪問的。
//例如
let iframeSon = document.getElementById("myIframe").contentWindow.document.getElementById("son");
console.log(iframeSon)
如果是iframe和本頁面為不同的源頁面的情況下
我們就會出現(xiàn)上圖的報錯
2.XmlHttpRequest同源策略:禁止使用XHR對象向不同源的服務(wù)器地址發(fā)起HTTP請求悯舟。只要協(xié)議担租、域名、端口有任何一個不同抵怎,都被當(dāng)作是不同的域奋救,之間的請求就是跨域操作。
AJAX同源策略主要用來防止CSRF攻擊反惕。如果沒有AJAX同源策略尝艘,相當(dāng)危險,我們發(fā)起的每一次HTTP請求都會帶上請求地址對應(yīng)的cookie姿染,那么可以做如下攻擊:
- 用戶登錄了自己的銀行頁面 http://mybank.com背亥,http://mybank.com向用戶的cookie中添加用戶標(biāo)識。
- 用戶瀏覽了惡意頁面 http://evil.com悬赏。執(zhí)行了頁面中的惡意AJAX請求代碼狡汉。
- http://evil.com向http://mybank.com發(fā)起AJAX HTTP請求,請求會默認把http://mybank.com對應(yīng)cookie也同時發(fā)送過去舷嗡。
- 銀行頁面從發(fā)送的cookie中提取用戶標(biāo)識轴猎,驗證用戶無誤嵌莉,response中返回請求數(shù)據(jù)进萄。此時數(shù)據(jù)就泄露了。
- 而且由于Ajax在后臺執(zhí)行锐峭,用戶無法感知這一過程中鼠。
DOM同源策略也一樣,如果iframe之間可以跨域訪問沿癞,可以這樣攻擊:
- 做一個假網(wǎng)站援雇,里面用iframe嵌套一個銀行網(wǎng)站 http://mybank.com。
- 把iframe寬高啥的調(diào)整到頁面全部椎扬,這樣用戶進來除了域名惫搏,別的部分和銀行的網(wǎng)站沒有任何差別具温。
- 這時如果用戶輸入賬號密碼,我們的主網(wǎng)站可以跨域訪問到http://mybank.com的dom節(jié)點筐赔,就可以拿到用戶的輸入了铣猩,那么就完成了一次攻擊。
(如果賬號密碼你感覺不會讓你損失錢茴丰,你可以試著輸入一下信用卡卡號+安全碼达皿,然后你就能感覺到花錢的快樂)
所以說有了跨域跨域限制之后,我們才能更安全的上網(wǎng)了贿肩。
CORS是一個W3C標(biāo)準峦椰,全稱是"跨域資源共享"(Cross-origin resource sharing)。
它允許瀏覽器向跨源服務(wù)器汰规,發(fā)出[XMLHttpRequest
]請求汤功,從而克服了AJAX只能[同源]使用的限制。
下面是一個例子溜哮,瀏覽器發(fā)現(xiàn)這次跨源AJAX請求是簡單請求冤竹,就自動在頭信息之中,添加一個Origin字段茬射。
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
對于客戶端鹦蠕,我們還是正常使用xhr對象發(fā)送ajax請求。
唯一需要注意的是在抛,我們需要設(shè)置我們的xhr屬性withCredentials為true钟病,不然的話,cookie是帶不過去的刚梭,
設(shè)置:
xhr.withCredentials = true;
對于服務(wù)器端肠阱,需要在 response header中設(shè)置如下兩個字段:
Access-Control-Allow-Origin: [http://www.yourhost.com](https://link.zhihu.com/?target=http%3A//www.yourhost.com)
Access-Control-Allow-Credentials:true
這樣,我們就可以跨域請求接口了朴读。
jsonp實現(xiàn)跨域
基本原理就是通過動態(tài)創(chuàng)建script標(biāo)簽,然后利用src屬性進行跨域屹徘。
demo.js
// 定義一個fun函數(shù)
function fun(fata) {
console.log(data);
};
// 創(chuàng)建一個腳本,并且告訴后端回調(diào)函數(shù)名叫fun
var body = document.getElementsByTagName('body')[0];
var script = document.gerElement('script');
script.type = 'text/javasctipt';
script.src = 'demo.js?callback=fun';
body.appendChild(script);
這樣就能獲取到數(shù)據(jù)的同時直接執(zhí)行demo.js中的fun函數(shù)
postMessage實現(xiàn)跨域
ES5新增的 postMessage() 方法允許來自不同源的腳本采用異步方式進行有限的通信衅金,可以實現(xiàn)跨文本檔噪伊、多窗口、跨域消息傳遞.
語法: postMessage(data,origin)
data: 要傳遞的數(shù)據(jù)氮唯,html5規(guī)范中提到該參數(shù)可以是JavaScript的任意基本類型或可復(fù)制的對象鉴吹,然而并不是所有瀏覽器都做到了這點兒,部分瀏覽器只能處理字符串參數(shù)惩琉,所以我們在傳遞參數(shù)的時候建議使用JSON.stringify()方法對對象參數(shù)序列化豆励,在低版本IE中引用json2.js可以實現(xiàn)類似效果.
origin:字符串參數(shù),指明目標(biāo)窗口的源瞒渠,協(xié)議+主機+端口號[+URL]良蒸,URL會被忽略技扼,所以可以不寫,這個參數(shù)是為了安全考慮嫩痰,postMessage()方法只會將message傳遞給指定窗口淮摔,當(dāng)然如果愿意也可以建參數(shù)設(shè)置為”*”,這樣可以傳遞給任意窗口始赎,如果要指定和當(dāng)前窗口同源的話設(shè)置為”/“和橙。
父頁面發(fā)送消息:
window.frames[0].postMessage('message', origin)
iframe接受消息:
window.addEventListener('message',function(e){
if(e.source!=window.parent) return;//若消息源不是父頁面則退出
//TODO ...
});
其中 e 對象有三個重要的屬性
1.data, 表示父頁面?zhèn)鬟f過來的message
2.source, 表示發(fā)送消息的窗口對象
3.origin, 表示發(fā)送消息窗口的源(協(xié)議+主機+端口號)
document.domain來進行跨域
通過修改document的domain屬性,我們可以在域和子域或者不同的子域之間通信(即它們必須在同一個一級域名下). 同域策略認為域和子域隸屬于不同的域造垛,比如a.com和 script.a.com是不同的域魔招,這時,我們無法在a.com下的頁面中調(diào)用script.a.com中定義的JavaScript方法五辽。但是當(dāng)我們把它們document的domain屬性都修改為a.com办斑,瀏覽器就會認為它們處于同一個域下,那么我們就可以互相獲取對方數(shù)據(jù)或者操作對方DOM了杆逗。
比如, 我們在 www.a.com/a.html 下, 現(xiàn)在想獲取 www.script.a.com/b.html, 即主域名相同, 二級域名不同. 那么可以這么做:
document.domain = 'a.com';
var iframe = document.createElement('iframe');
iframe.src = 'http://www.script.a.com/b.html';
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.addEventListener('load',function(){
//TODO 載入完成時做的事情
//var _document = iframe.contentWindow.document;
//...
},false);
注意:
2個頁面都要設(shè)置, 哪怕 a.html 頁已處于 a.com 域名下, 也必須顯式設(shè)置.
document.domain只能設(shè)置為一級域名乡翅,比如這里a頁不能設(shè)置為www.a.com (二級域名).
利用domain屬性跨域具有以下局限性:
兩個頁面要在同一個一級域名下, 且必須同協(xié)議, 同端口, 即子域互跨;
只適用于iframe(幾乎沒luan用 ̄へ ̄)
Internet Explorer同源策略繞過
Internet Explorer8以及前面的版本很容易通過document.domain實現(xiàn)同源策略繞過, 通過重寫文檔對象, 域?qū)傩赃@個問題可以十分輕松的被利用.
var document;
document = {};
document.domain = 'http://www.a.com';
console.log(document.domain);
如果你在最新的瀏覽器中運行這段代碼, 可能在JavaScript控制臺會顯示一個同源策略繞過錯誤(真正沒luan用(艸皿艸))
底下的文章中還有幾個不常用的方法,都幾乎是在特定的情況下才能生效罪郊,就不一一解釋了蠕蚜。
那么我們插件中又是怎么解決跨域的呢?
在插件中的background是不受域的限制的悔橄,因此可以輕松發(fā)送ajax請求給其他網(wǎng)站靶累。
background的權(quán)限非常高,幾乎可以調(diào)用所有的Chrome擴展API(除了devtools)癣疟,而且它可以無限制跨域挣柬,也就是可以跨域訪問任何網(wǎng)站而無需要求對方設(shè)置CORS。
經(jīng)過測試睛挚,其實不止是background邪蛔,所有的直接通過chrome-extension://id/xx.html這種方式打開的網(wǎng)頁都可以無限制跨域。
然后說說上周發(fā)現(xiàn)插件的一個坑
最近要做一個頁面可以內(nèi)嵌許多其他的外部網(wǎng)頁扎狱,于是使用了iframe
然后使用iframe的時候少許網(wǎng)頁會出現(xiàn)這種情況
那么這個X-Frame-Options又是個啥呢侧到?
X-Frame-Options是什么?
X-Frame-Options是一個HTTP標(biāo)頭(header)委乌,用來告訴瀏覽器這個網(wǎng)頁是否可以放在iFrame內(nèi)床牧。例如:
X-Frame-Options: DENY X-Frame-Options: SAMEORIGIN X-Frame-Options: ALLOW-FROM http://caibaojian.com/
第一個例子告訴瀏覽器不要(DENY)把這個網(wǎng)頁放在iFrame內(nèi),通常的目的就是要幫助用戶對抗點擊劫持遭贸。
第二個例子告訴瀏覽器只有當(dāng)架設(shè)iFrame的網(wǎng)站與發(fā)出X-Frame-Options的網(wǎng)站相同,才能顯示發(fā)出X-Frame-Options網(wǎng)頁的內(nèi)容心软。
第三個例子告訴瀏覽器這個網(wǎng)頁只能放在http://caibaojian.com//網(wǎng)頁架設(shè)的iFrame內(nèi)壕吹。
不指定X-Frame-Options的網(wǎng)頁等同表示它可以放在任何iFrame內(nèi)著蛙。
X-Frame-Options可以保障你的網(wǎng)頁不會被放在惡意網(wǎng)站設(shè)定的iFrame內(nèi),令用戶成為點擊劫持的受害人耳贬。
簡單的來說踏堡,這東西就是用來阻止自己的網(wǎng)頁被嵌套在iframe中然后被網(wǎng)站盜取資料的
但是如果你裝了chrome插件的話在background中進行一個這樣的設(shè)置,就可以繞過網(wǎng)站的設(shè)置
chrome.webRequest
使用 chrome.webRequest API 監(jiān)控與分析流量咒劲,還可以實時地攔截顷蟆、阻止或修改請求。
onBeforeRequest(可以為同步)
當(dāng)請求即將發(fā)出時產(chǎn)生腐魂。這一事件在 TCP 連接建立前發(fā)送帐偎,可以用來取消或重定向請求。
onBeforeSendHeaders(可以為同步)
當(dāng)請求即將發(fā)出并且初始標(biāo)頭已經(jīng)準備好時產(chǎn)生蛔屹。這一事件是為了使擴展程序能夠添加削樊、修改和刪除請求標(biāo)頭(*)。onBeforeSendHeaders 事件將傳遞給所有訂閱者兔毒,所以不同的訂閱者都可以嘗試修改請求漫贞。有關(guān)具體如何處理的細節(jié),請參見實現(xiàn)細節(jié)部分育叁。這一事件可以用來取消請求迅脐。
onSendHeaders
當(dāng)所有擴展程序已經(jīng)修改完請求標(biāo)頭并且展現(xiàn)最終版本時產(chǎn)生。這一事件在標(biāo)頭發(fā)送至網(wǎng)絡(luò)前觸發(fā)豪嗽,僅用于提供信息仪际,并且以異步方式處理,不允許修改或取消請求昵骤。
onHeadersReceived(可以為同步)
每當(dāng)接收到 HTTP(S) 響應(yīng)標(biāo)頭時產(chǎn)生树碱。由于重定向以及認證請求,對于每次請求這一事件可以多次產(chǎn)生变秦。這一事件是為了使擴展程序能夠添加成榜、修改和刪除響應(yīng)標(biāo)頭,例如傳入的 Set-Cookie 標(biāo)頭蹦玫。緩存指示是在該事件觸發(fā)前處理的赎婚,所以修改 Cache-Control 之類的標(biāo)頭不會影響瀏覽器的緩存。它還允許您重定向請求樱溉。
onAuthRequired(可以為同步)
當(dāng)請求需要用戶認證時產(chǎn)生挣输。這一事件可以同步處理,提供認證憑據(jù)福贞。注意撩嚼,擴展程序提供的憑據(jù)可能無效,注意不要重復(fù)提供無效憑據(jù),陷入無限循環(huán)完丽。
onBeforeRedirect
當(dāng)重定向即將執(zhí)行時產(chǎn)生恋技,重定向可以由 HTTP 響應(yīng)代碼或擴展程序觸發(fā)。這一事件僅用于提供信息逻族,并以異步方式處理蜻底,不允許修改或取消請求。
onResponseStarted
當(dāng)接收到響應(yīng)正文的第一個字節(jié)時產(chǎn)生聘鳞。對于 HTTP 請求薄辅,這意味著狀態(tài)行和響應(yīng)標(biāo)頭已經(jīng)可用。這一事件僅用于提供信息抠璃,并以異步方式處理站楚,不允許修改或取消請求。
onCompleted
當(dāng)請求成功處理后產(chǎn)生鸡典。
onErrorOccurred
當(dāng)請求不能成功處理時產(chǎn)生源请。
網(wǎng)絡(luò)請求 API 保證對于每一個請求,onCompleted 或 onErrorOccurred 是最終產(chǎn)生的事件彻况,除了如下例外:如果請求重定向至 data:// URL谁尸,######onBeforeRedirect 將是最后報告的事件。
這里我們主要使用的是onHeadersReceived
chrome.webRequest.onHeadersReceived.addListener(
(details)=> {
for (var i = 0; i < details.responseHeaders.length; ++i) {
if (details.responseHeaders[i].name.toLowerCase() == 'x-frame-options') {
details.responseHeaders.splice(i, 1);
return {
responseHeaders: details.responseHeaders
};
}
}
}, {
urls: ["*://zhihu.com/*"]
}, ["blocking", "responseHeaders"]);
這里我們主要的就是用onSendHeaders纽甘,大家可以看到代碼中良蛮,每當(dāng)接收到 HTTP(S) 響應(yīng)標(biāo)頭時,將會獲取到其中的responseHeaders悍赢,然后將其中的‘x-frame-options’這個設(shè)置給砍掉决瞳。這樣瀏覽器的最終結(jié)果就成功繞過了iframe的設(shè)置,強行的將網(wǎng)頁變成內(nèi)嵌的iframe左权。
至于瀏覽器的安全問題皮胡。。赏迟。屡贺。。
锌杀。甩栈。
你都裝了這么流氓的插件了,我們網(wǎng)站能咋辦糕再,自己背鍋量没。
文章資料來源
http://www.ruanyifeng.com/blog/2016/04/cors.html
http://louiszhai.github.io/2016/01/11/cross-domain/
https://crxdoc-zh.appspot.com/extensions/webRequest#implementation