您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“瀏覽器的面試題有哪些”,內(nèi)容詳細,步驟清晰,細節(jié)處理妥當,希望這篇“瀏覽器的面試題有哪些”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
瀏覽器的內(nèi)核可以分成兩部分:
渲染引擎
和JS引擎
(注意:我們常說的瀏覽器內(nèi)核就是指渲染引擎)
由于JS引擎越來越獨立,內(nèi)核就指的只是渲染引擎了,渲染引擎主要用來請求網(wǎng)絡頁面資源解析排版后呈現(xiàn)給用戶
瀏覽器/RunTime | 內(nèi)核(渲染引擎) | JavaScript 引擎 |
---|---|---|
Chrome | Blink(28~) Webkit(Chrome 27) | V8 |
FireFox | Gecko | SpiderMonkey |
Safari | Webkit | JavaScriptCore |
Edge | EdgeHTML | Chakra(For JavaScript) |
IE | Trident | Chakra(For JScript) |
Opera | Presto->blink | Linear A(4.0-6.1)/ Linear B(7.0-9.2)/ Futhark(9.5-10.2)/ Carakan(10.5-) |
Node.js | - | V8 |
用戶界面:包括地址欄,前進/后退/刷新/書簽等按鈕
瀏覽器引擎:在用戶界面和呈現(xiàn)引擎之間傳送指令
渲染引擎:用來繪制請求的內(nèi)容
網(wǎng)絡:用來完成網(wǎng)絡調(diào)用,例如http請求,它具有平臺無關(guān)的接口,可以在不同平臺上工作
JavaScript解釋器:用來解析執(zhí)行JavaScript代碼
用戶界面后端:用于繪制基本的窗口小部件,比如組合框和窗口,底層使用操作系統(tǒng)的用戶接口
數(shù)據(jù)存儲:屬于持久層,瀏覽器在硬盤中保存類似cookie的各種數(shù)據(jù),HTML5定義了web database技術(shù),這是一種輕量級完整的客戶端存儲技術(shù)
注意:與大多數(shù)瀏覽器不同的是,谷歌(Chrome)瀏覽器的每個標簽頁都分別對應一個呈現(xiàn)引擎實例。每個標簽頁都是一個獨立的進程
這個題可以說是面試最常見也是一道可以無限難的題了,一般面試官出這道題就是為了考察你的前端知識深度。
1、瀏覽器接受URL開啟網(wǎng)絡請求線程(涉及到:瀏覽器機制,線程與進程等)
2、開啟網(wǎng)絡線程到發(fā)出一個完整的http請求(涉及到:DNS查詢,TCP/IP請求,5層網(wǎng)絡協(xié)議等)
3、從服務器接收到請求到對應后臺接受到請求(涉及到:負載均衡,安全攔截,后臺內(nèi)部處理等)
4、后臺與前臺的http交互(涉及到:http頭,響應碼,報文結(jié)構(gòu),cookie等)
5、緩存問題(涉及到:http強緩存與協(xié)商緩存,緩存頭,etag,expired,cache-control等)
6、瀏覽器接受到http數(shù)據(jù)包后的解析流程(涉及到html詞法分析,解析成DOM樹,解析CSS生成CSSOM樹,合并生成render渲染樹。然后layout布局,painting渲染,復合圖層合成,GPU繪制,外鏈處理等)
7、css可視化模型(涉及到:元素渲染規(guī)則,如:包含塊,控制框,BFC,IFC等)
8、JS引擎解析過程(涉及到:JS解析階段,預處理階段,執(zhí)行階段生成執(zhí)行上下文,VO(全局對象),作用域鏈,回收機制等)
你會發(fā)現(xiàn)一個簡單的輸入URL到頁面呈現(xiàn),之間會發(fā)生這么多過程,是不是瞬間覺得崩潰了?(別急,這一章我們不講這么深,先教你如何回答這個問題,后面這一節(jié)單獨出文章講)
瀏覽器通過DNS服務器得到域名的IP地址,向這個IP地址請求得到HTML文本
瀏覽器渲染進程解析HTML文本,構(gòu)建DOM樹
解析HTML的同時,如果遇到內(nèi)聯(lián)樣式或者樣式文件,則下載并構(gòu)建樣式規(guī)則,如果遇到JavaScript腳本,則會下載執(zhí)行腳本
DOM樹和CSSOM構(gòu)建完成之后,渲染進程將兩者合并成渲染樹(render tree)
渲染進程開始對渲染樹進行布局,生成布局樹(layout tree)
渲染樹對布局樹進行繪制,生成繪制記錄
解析HTML
HTML是逐行解析的,瀏覽器的渲染引擎會將HTML文檔解析并轉(zhuǎn)換成DOM節(jié)點。
將HTML解析成許多Tokens
將Tokens解析成object
將object組合成一個DOM樹
解析CSS
瀏覽器會從右往左解析CSS選擇器
我們知道DOM樹與CSSOM樹合并成render樹,實際上是將CSSOM附著到DOM樹上,因此需要根據(jù)選擇器提供的信息對DOM樹進行遍歷。
我們看一個例子:
<style> .nav .title span {color:blue} </style> <div class='nav'> <div class='title'> <span>南玖</span> </div> <div class="sub_title">前端</header> </div>
從右至左的匹配:
先找到所有的最右節(jié)點 span,對于每一個 span,向上尋找節(jié)點 div.title
由 h4再向上尋找 div.nav 的節(jié)點
最后找到根元素 html 則結(jié)束這個分支的遍歷。
解析JS
在瀏覽器中有一個js解析器的工具,專門用來解析我們的js代碼。
當瀏覽器遇到js代碼時,立馬召喚“js解析器”出來工作。
解析器會找到js當中的所有變量、函數(shù)、參數(shù)等等,并且把變量賦值為未定義(undefined)。
把函數(shù)取出來成為一個函數(shù)塊,然后存放到倉庫當中。這件事情做完了之后才開始逐行解析代碼(由上向下,由左向右),然后再去和倉庫進行匹配。
DOMContentLoaded:僅當DOM解析完成后觸發(fā),不包括樣式表,圖片等資源。
Load:當頁面上所有的DOM,樣式表,腳本,圖片等資源加載完畢事觸發(fā)。
重排: 部分渲染樹或整個渲染樹需要重新分析且節(jié)點尺寸需要重新計算,表現(xiàn)為重新生成布局,重新排列元素
重繪: 由于節(jié)點的幾何屬性發(fā)生改變或樣式改變,例如元素背景元素,表現(xiàn)為某些元素的外觀被改變
重繪不一定導致重排,但重排一定繪導致重繪
如何觸發(fā)重繪和重排?
任何改變用來構(gòu)建渲染樹的信息都會導致一次重排或重繪:
添加、刪除、更新DOM節(jié)點
通過display: none隱藏一個DOM節(jié)點-觸發(fā)重排和重繪
通過visibility: hidden隱藏一個DOM節(jié)點-只觸發(fā)重繪,因為沒有幾何變化
移動或者給頁面中的DOM節(jié)點添加動畫
添加一個樣式表,調(diào)整樣式屬性
用戶行為,例如調(diào)整窗口大小,改變字號,或者滾動。
如何避免重繪或重排?
集中改變樣式:比如使用class的方式來集中改變樣式
使用document.createDocumentFragment()
:我們可以通過createDocumentFragment創(chuàng)建一個游離于DOM樹之外的節(jié)點,然后在此節(jié)點上批量操作,最后插入DOM樹中,因此只觸發(fā)一次重排
提升為合成層
將元素提升為合成層有以下優(yōu)點:
合成層的位圖,會交由 GPU
合成,比 CPU
處理要快
當需要repaint
時,只需要 repaint
本身,不會影響到其他的層
對于 transform
和 opacity
效果,不會觸發(fā) layout
和 paint
提升合成層的最好方式是使用 CSS 的 will-change
屬性
這主要與JS的用途有關(guān),JS作為瀏覽器的腳本語言,最初主要是實現(xiàn)用戶與瀏覽器的交互,以及操作DOM。這就決定了它只能是單線程,否則會帶來許多復雜的同步問題。
舉個例子: 如果JS是多線程的,其中一個線程要修改一個DOM元素,另外一個線程想要刪除這個DOM元素,這時候瀏覽器就不知道該聽誰的。所以為了避免復雜性,從一誕生,JavaScript就被設計成單線程。
為了利用多核CPU的計算能力,HTML5提出Web Worker標準,允許JavaScript腳本創(chuàng)建多個線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個新標準并沒有改變JavaScript單線程的本質(zhì)
先上結(jié)論
CSS
不會阻塞DOM
的解析,但會阻塞DOM
的渲染
CSS
會阻塞JS
執(zhí)行,但不會阻塞JS
文件的下載
CSSOM的作用
第一個是提供給JavaScript操作樣式表的能力
第二個是為布局樹的合成提供基礎的樣式信息
這個CSSOM體現(xiàn)在DOM中就是document.styleSheets
由之前講到的瀏覽器渲染流程我們可以看出:
DOM和CSSOM通常是并行構(gòu)建的,所以CSS加載不會阻塞DOM的解析
render樹是依賴DOM樹和CSSOM樹的,所以它必須等到兩者都加載完畢才能開始構(gòu)建渲染,所以CSS加載會阻塞DOM的渲染
由于JavaScript是可以操作DOM與CSS的,如果在修改這些元素屬性同時渲染界面(即JavaScript線程與UI線程同時進行),那么渲染線程前后獲得的元素可能就不一致了。所以為了防止渲染出現(xiàn)不可預期的結(jié)果,瀏覽器設置GUI渲染線程與JavaScript線程為互斥的關(guān)系
JS需要等待CSS的下載,這是為什么呢?(CSS阻塞DOM執(zhí)行)
如果JS
腳本的內(nèi)容是獲取元素的樣式,那它就必然依賴CSS
。因為瀏覽器無法感知JS
內(nèi)部到底想干什么,為避免樣式獲取,就只好等前面所有的樣式下載完畢再執(zhí)行JS
。但JS文件與CSS文件下載是并行的,CSS文件會在后面的JS文件執(zhí)行前先加載執(zhí)行完畢,所以CSS會阻塞后面JS的執(zhí)行
避免白屏,提高CSS的加載速度
使用CDN(CDN會根據(jù)你的網(wǎng)絡狀況,挑選最近的一個具有緩存內(nèi)容的節(jié)點為你提供資源,因此可以減少加載時間)
對CSS進行壓縮
合理使用緩存
減少http請求數(shù),合并CSS文件
先上結(jié)論
JS會阻塞DOM的解析,因此也就會阻塞頁面的加載
這也是為什么我們常說要把JS文件放在最下面的原因
由于 JavaScript 是可操縱 DOM 的,如果在修改這些元素屬性同時渲染界面(即 JavaScript 線程和 UI 線程同時運行),那么渲染線程前后獲得的元素數(shù)據(jù)就可能不一致了。
因此為了防止渲染出現(xiàn)不可預期的結(jié)果,瀏覽器設置 **「GUI 渲染線程與 JavaScript 引擎為互斥」**的關(guān)系。
當 JavaScript 引擎執(zhí)行時 GUI 線程會被掛起,GUI 更新會被保存在一個隊列中等到引擎線程空閑時立即被執(zhí)行。
當瀏覽器在執(zhí)行 JavaScript 程序的時候,GUI 渲染線程會被保存在一個隊列中,直到 JS 程序執(zhí)行完成,才會接著執(zhí)行。
因此如果 JS 執(zhí)行的時間過長,這樣就會造成頁面的渲染不連貫,導致頁面渲染加載阻塞的感覺。
兩者都是異步去加載外部JS文件,不會阻塞DOM解析
Async是在外部JS加載完成后,瀏覽器空閑時,Load事件觸發(fā)前執(zhí)行,標記為async的腳本并不保證按照指定他們的先后順序執(zhí)行,該屬性對于內(nèi)聯(lián)腳本無作用 (即沒有**「src」**屬性的腳本)。
defer是在JS加載完成后,整個文檔解析完成后,觸發(fā) DOMContentLoaded
事件前執(zhí)行,如果缺少 src
屬性(即內(nèi)嵌腳本),該屬性不應被使用,因為這種情況下它不起作用
垃圾回收是一種自動的內(nèi)存管理機制。當計算機上的動態(tài)內(nèi)存不再需要時,就應該予以釋放。
需要注意的是,自動的意思是瀏覽器可以自動幫助我們回收內(nèi)存垃圾,但并不代表我們不用關(guān)心內(nèi)存管理,如果操作不當,JavaScript中仍然會出現(xiàn)內(nèi)存溢出的情況,造成系統(tǒng)崩潰。
由于字符串,數(shù)組,對象等都沒有固定大小,因此需要當它們大小已知時,才能對他們進行動態(tài)的存儲分配。JavaScript程序每次創(chuàng)建字符串,數(shù)組或?qū)ο髸r,解釋器都必須分配內(nèi)存來存儲那個實體。
JavaScript解釋器可以檢測到何時程序不在使用一個對象了,當它確定這個對象是無用的時候,他就知道不再需要這個對象了,就可以把它占用的內(nèi)存釋放掉了。
瀏覽器通常采用的垃圾回收有兩種方法:標記清除,引用計數(shù)。
標記清除
這是JavaScript中最常用的垃圾回收方式
從2012年起,所有現(xiàn)代瀏覽器都使用了標記清除的垃圾回收方法,除了低版本IE還是采用的引用計數(shù)法。
那么什么叫標記清除呢?
JavaScript中有一個全局對象,定期的,垃圾回收器將從這個全局對象開始,找出所有從這個全局對象開始引用的對象,再找這些對象引用的對象...對這些活躍的對象標記,這是標記階段。清楚階段就是清楚那些沒有被標記的對象。
標記清除有一個問題,就是在清除之后,內(nèi)存空間是不連續(xù)的,即出現(xiàn)了內(nèi)存碎片。如果后面需要一個比較大的連續(xù)的內(nèi)存空間,那將不能滿足要求。而標記整理 方法可以有效德地解決這個問題。
在標記的過程中,引入了概念:三色標記法,三色為:
白:未被標記的對象,即不可達對象(沒有掃描到的對象),可回收
灰:已被標記的對象(可達對象),但是對象還沒有被掃描完,不可回收
黑:已被掃描完(可達對象),不可回收
標記整理:
標記階段與標記清除法沒什么區(qū)別,只是標記結(jié)束后,標記整理法會將存活的對象向內(nèi)存的一邊移動,最后清理掉邊界內(nèi)存。
引用計數(shù)
引用計數(shù)的含義是跟蹤記錄每個值被引用的次數(shù)。當一個變量A被賦值時,這個值的引用次數(shù)就是1,當變量A重新賦值后,則之前那個值的引用次數(shù)就減1。當引用次數(shù)變成0時,則說明沒有辦法再訪問這個值了,所以就可以清除這個值占用的內(nèi)存了。
大多數(shù)瀏覽器已經(jīng)放棄了這種回收方式
內(nèi)存泄漏
為避免內(nèi)存泄漏,一旦數(shù)據(jù)不再使用,最好通過將其值設為
null
來釋放其引用,這個方法叫做接觸引用
哪些情況會造成內(nèi)存泄漏?如何避免?
以 Vue 為例,通常有這些情況:
監(jiān)聽在 window/body
等事件沒有解綁
綁在 EventBus
的事件沒有解綁
Vuex
的 $store
,watch
了之后沒有 unwatch
使用第三方庫創(chuàng)建,沒有調(diào)用正確的銷毀函數(shù)
解決辦法:beforeDestroy
中及時銷毀
綁定了 DOM/BOM
對象中的事件 addEventListener
,removeEventListener
。
觀察者模式 $on
,$off
處理。
如果組件中使用了定時器,應銷毀處理。
如果在 mounted/created
鉤子中使用了第三方庫初始化,對應的銷毀。
使用弱引用 weakMap
、weakSet
。
瀏覽器中不同類型變量的內(nèi)存都是何時釋放的?
引用類型
在沒有引用之后,通過 V8 自動回收。
基本類型
如果處于閉包的情況下,要等閉包沒有引用才會被 V8 回收。
非閉包的情況下,等待 V8 的新生代切換的時候回收。
認識瀏覽器緩存
當瀏覽器請求一個網(wǎng)站時,會加載各種資源,對于一些不經(jīng)常變動的資源,瀏覽器會將他們保存在本地內(nèi)存中,下次訪問時直接加載這些資源,提高訪問速度。
如何知道資源是請求的服務器還是讀取的緩存呢?
看上面這張圖,有些資源的size值是大小,有些是from disk cache
,有些是from memory cache
,顯示大小的是請求的服務器資源,而顯示后面兩種的則是讀取的緩存。
disk cache: 就是將資源存儲在磁盤中,等待下次訪問時不需重新下載,直接從磁盤中讀取,它的直接操作對象為CurlCacheManager
。(效率比內(nèi)存緩存慢,但存儲容量大,存儲時間長)
memory cache: 就是將資源緩存到內(nèi)存中,等待下次訪問時不需重新下載,直接從內(nèi)存中讀取。(從效率上看它是最快的,從存活時間來看,它是最短的。)
- | memory cache | disk cache |
---|---|---|
相同點 | 只能存儲一些派生類資源文件 | 只能存儲一些派生類資源文件 |
不同點 | 退出進程時數(shù)據(jù)會被清除 | 退出進程時數(shù)據(jù)不會被清除 |
存儲資源 | 一般腳本、字體、圖片會存在內(nèi)存當中 | 一般非腳本會存在內(nèi)存當中,如css等 |
瀏覽器緩存分類
強緩存
協(xié)商緩存
瀏覽器在向服務器請求資源時,首先判斷是否命中強緩存,沒命中再判斷是否命中協(xié)商緩存
強緩存
瀏覽器在加載資源時,會先根據(jù)本地緩存資源的header
中判斷是否命中強緩存,如果命中則直接使用緩存中的資源,不會再向服務器發(fā)送請求。 (這里的header中的信息指的是 expires
和 cache-control
)
Expires
該字段是 http1.0 時的規(guī)范,它的值為一個絕對時間的 GMT 格式的時間字符串,比如 Expires:Mon,18 Oct 2066 23:59:59 GMT。這個時間代表著這個資源的失效時間,在此時間之前,即命中緩存。這種方式有一個明顯的缺點,由于失效時間是一個絕對時間,所以當服務器與客戶端時間偏差較大時,就會導致緩存混亂。所以這種方式很快在后來的HTTP1.1版本中被拋棄了。
Cache-Control
Cache-Control 是 http1.1 時出現(xiàn)的 header 信息,主要是利用該字段的 max-age 值來進行判斷,它是一個相對時間,例如 Cache-Control:max-age=3600
,代表著資源的有效期是 3600 秒。cache-control 除了該字段外,還有下面幾個比較常用的設置值:
no-cache:需要進行協(xié)商緩存,發(fā)送請求到服務器確認是否使用緩存。
no-store:禁止使用緩存,每一次都要重新請求數(shù)據(jù)。
public:可以被所有的用戶緩存,包括終端用戶和 CDN 等中間代理服務器。
private:只能被終端用戶的瀏覽器緩存,不允許 CDN 等中繼緩存服務器對其緩存。
Cache-Control 與 Expires 可以在服務端配置同時啟用,同時啟用的時候 Cache-Control 優(yōu)先級高。
協(xié)商緩存
當強緩存沒命中時,瀏覽器會發(fā)送一個請求到服務器,服務器根據(jù) header
中的信息來判斷是否命中協(xié)商緩存。如果命中,則返回304 ,告訴瀏覽器資源未更新,可以使用本地緩存。 (這里的header信息指的是Last-Modify/If-Modify-Since
和 ETag/If-None-Match
)
Last-Modify/If-Modify-Since
瀏覽器第一次請求一個資源的時候,服務器返回的 header 中會加上 Last-Modify,Last-modify 是一個時間標識該資源的最后修改時間。
當瀏覽器再次請求該資源時,request 的請求頭中會包含 If-Modify-Since,該值為緩存之前返回的 Last-Modify。服務器收到 If-Modify-Since 后,根據(jù)資源的最后修改時間判斷是否命中緩存。
如果命中緩存,則返回 304,并且不會返回資源內(nèi)容,并且不會返回 Last-Modify。
缺點:
短時間內(nèi)資源發(fā)生了改變,Last-Modified 并不會發(fā)生變化。
周期性變化。如果這個資源在一個周期內(nèi)修改回原來的樣子了,我們認為是可以使用緩存的,但是 Last-Modified 可不這樣認為,因此便有了 ETag。
ETag/If-None-Match
與 Last-Modify/If-Modify-Since 不同的是,Etag/If-None-Match 返回的是一個校驗碼。ETag 可以保證每一個資源是唯一的,資源變化都會導致 ETag 變化。服務器根據(jù)瀏覽器上送的 If-None-Match 值來判斷是否命中緩存。
與 Last-Modified 不一樣的是,當服務器返回 304 Not Modified 的響應時,由于 ETag 重新生成過,response header 中還會把這個 ETag 返回,即使這個 ETag 跟之前的沒有變化。
Last-Modified 與 ETag 是可以一起使用的,服務器會優(yōu)先驗證 ETag,一致的情況下,才會繼續(xù)比對 Last-Modified,最后才決定是否返回 304。
總結(jié)
當瀏覽器訪問一個已經(jīng)訪問過的資源是,它的步驟是:
1.先看是否命中強緩存,命中?的話直接使用緩存
2.沒命中強緩存,則會發(fā)送請求到服務器看是否命中?協(xié)商緩存
3.如果命中了協(xié)商緩存,服務器會返回304告訴瀏覽器可以使用本地緩存
4.沒命中協(xié)商緩存,則服務器會返回新的資源給瀏覽器
同源策略
同源策略是瀏覽器的一種自我保護行為。所謂的同源指的是:協(xié)議,域名,端口均要相同
瀏覽器中大部分內(nèi)容都是受同源策略限制的,但是以下三個標簽不受限制:
<img src="..." /> <link href="..." /> <script src="..."></script>
跨域
跨域指的是瀏覽器不能執(zhí)行其它域名下的腳本。它是由瀏覽器的同源策略限制的。
你可能會想跨域請求到底有沒有發(fā)送到服務器?
事實上,跨域請求時能夠發(fā)送到服務器的,并且服務器也能過接受的請求并正常返回結(jié)果,只是結(jié)果被瀏覽器攔截了。
JSONP
它主要是利用script標簽不受瀏覽器同源策略的限制,可以拿到從其他源傳輸過來的數(shù)據(jù),需要服務端支持。
優(yōu)缺點:
兼容性比較好,可用于解決主流瀏覽器的跨域數(shù)據(jù)訪問的問題。缺點就是僅支持get請求,具有局限性,不安全,可能會受到XSS攻擊。
思路:
聲明一個回調(diào)函數(shù),其函數(shù)名(如show)當做參數(shù)值,要傳遞給跨域請求數(shù)據(jù)的服務器,函數(shù)形參為要獲取目標數(shù)據(jù)(服務器返回的data)。
創(chuàng)建一個<script>
標簽,把那個跨域的API數(shù)據(jù)接口地址,賦值給script的src,還要在這個地址中向服務器傳遞該函數(shù)名(可以通過問號傳參:?callback=show)。
服務器接收到請求后,需要進行特殊的處理:把傳遞進來的函數(shù)名和它需要給你的數(shù)據(jù)拼接成一個字符串,例如:傳遞進去的函數(shù)名是show,它準備好的數(shù)據(jù)是show('南玖')
。
最后服務器把準備的數(shù)據(jù)通過HTTP協(xié)議返回給客戶端,客戶端再調(diào)用執(zhí)行之前聲明的回調(diào)函數(shù)(show),對返回的數(shù)據(jù)進行操作。
// front function jsonp({ url, params, callback }) { return new Promise((resolve, reject) => { let script = document.createElement('script') window[callback] = function(data) { resolve(data) document.body.removeChild(script) } params = { ...params, callback } // wd=b&callback=show let arrs = [] for (let key in params) { arrs.push(`${key}=${params[key]}`) } script.src = `${url}?${arrs.join('&')}` document.body.appendChild(script) }) } jsonp({ url: 'http://localhost:3000/say', params: { wd: 'wxgongzhonghao' }, callback: 'show' }).then(data => { console.log(data) })
// server 借助express框架 let express = require('express') let app = express() app.get('/say', function(req, res) { let { wd, callback } = req.query console.log(wd) // Iloveyou console.log(callback) // show res.end(`${callback}('關(guān)注前端南玖')`) }) app.listen(3000)
上面這段代碼相當于向http://localhost:3000/say?wd=wxgongzhonghao&callback=show
這個地址請求數(shù)據(jù),然后后臺返回show('關(guān)注前端南玖')
,最后會運行show()這個函數(shù),打印出'關(guān)注前端南玖'
跨域資源共享(CORS)
CORS(Cross-Origin Resource Sharing)跨域資源共享,定義了必須在訪問跨域資源時,瀏覽器與服務器應該如何溝通。CORS背后的基本思想是使用自定義的HTTP頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功還是失敗。
CORS 需要瀏覽器和后端同時支持。IE 8 和 9 需要通過 XDomainRequest 來實現(xiàn)。
瀏覽器會自動進行 CORS 通信,實現(xiàn) CORS 通信的關(guān)鍵是后端。只要后端實現(xiàn)了 CORS,就實現(xiàn)了跨域。
服務端設置 Access-Control-Allow-Origin 就可以開啟 CORS。 該屬性表示哪些域名可以訪問資源,如果設置通配符則表示所有網(wǎng)站都可以訪問資源。
雖然設置 CORS 和前端沒什么關(guān)系,但是通過這種方式解決跨域問題的話,會在發(fā)送請求時出現(xiàn)兩種情況,分別為簡單請求和復雜請求。
簡單請求: (滿足以下兩個條件,就是簡單請求)
1.請求方法為以下三個之一:
GET
POST
HEAD
2.Content-Type的為以下三個之一:
text-plain
multiparty/form-data
application/x-www-form-urlencoded
復雜請求:
不是簡單請求那它肯定就是復雜請求了。復雜請求的CORS請求,會在正式發(fā)起請求前,增加一次HTTP查詢請求,稱為預檢 請求,該請求是option方法的,通過該請求來知道服務端是否允許該跨域請求。
Nginx 反向代理的原理很簡單,即所有客戶端的請求都必須經(jīng)過nginx處理,nginx作為代理服務器再將請求轉(zhuǎn)發(fā)給后端,這樣就規(guī)避了瀏覽器的同源策略。
什么是XSS?
XSS 全稱是
Cross Site Scripting
,為了與css
區(qū)分開來,所以簡稱XSS
,中文叫作跨站腳本XSS是指黑客往頁面中注入惡意腳本,從而在用戶瀏覽頁面時利用惡意腳本對用戶實施攻擊的一種手段。
XSS能夠做什么?
竊取Cookie
監(jiān)聽用戶行為,比如輸入賬號密碼后之間發(fā)給黑客服務器
在網(wǎng)頁中生成浮窗廣告
修改DOM偽造登入表單
XSS實現(xiàn)方式
存儲型XSS攻擊
反射型XSS攻擊
基于DOM的XSS攻擊
如何阻止XSS攻擊?
對輸入腳本進行過濾或轉(zhuǎn)碼
對用戶輸入的信息過濾或者轉(zhuǎn)碼,保證用戶輸入的內(nèi)容不能在HTML解析的時候執(zhí)行。
利用CSP
該安全策略的實現(xiàn)基于一個稱作
Content-Security-Policy
的HTTP首部。(瀏覽器內(nèi)容安全策略)它的核心思想就是服務器決定瀏覽器加載那些資源。
限制加載其他域下的資源文件,這樣即使黑客插入了一個 JavaScript 文件,這個 JavaScript 文件也是無法被加載的;
禁止向第三方域提交數(shù)據(jù),這樣用戶數(shù)據(jù)也不會外泄;
提供上報機制,能幫助我們及時發(fā)現(xiàn) XSS 攻擊。
禁止執(zhí)行內(nèi)聯(lián)腳本和未授權(quán)的腳本;
利用 HttpOnly
由于很多 XSS 攻擊都是來盜用 Cookie 的,因此還可以通過使用 HttpOnly 屬性來保護我們 Cookie 的安全。這樣子的話,JavaScript 便無法讀取 Cookie 的值。這樣也能很好的防范 XSS 攻擊。
通常服務器可以將某些 Cookie 設置為 HttpOnly 標志,HttpOnly 是服務器通過 HTTP 響應頭來設置的,下面是打開 Google 時,HTTP 響應頭中的一段:
set-cookie: NID=189=M8l6-z41asXtm2uEwcOC5oh9djkffOMhWqQrlnCtOI; expires=Sat, 18-Apr-2020 06:52:22 GMT; path=/; domain=.google.com; HttpOnly
對于不受信任的輸入,可以限制輸入長度
什么是CSRF攻擊?
CSRF 全稱
Cross-site request forgery
,中文為跨站請求偽造 ,攻擊者誘導受害者進入第三方網(wǎng)站,在第三方網(wǎng)站中,向被攻擊網(wǎng)站發(fā)送跨站請求。利用受害者在被攻擊網(wǎng)站已經(jīng)獲取的注冊憑證,繞過后臺的用戶驗證,達到冒充用戶對被攻擊的網(wǎng)站執(zhí)行某項操作的目的。 CSRF攻擊就是黑客利用用戶的登錄狀態(tài),并通過第三方站點來干一些嘿嘿嘿的壞事。
幾種常見的攻擊類型
1.GET類型的CSRF
GET類型的CSRF非常簡單,通常只需要一個HTTP請求:
<img src="http://bank.example/withdraw?amount=10000&for=hacker" >
在受害者訪問含有這個img的頁面后,瀏覽器會自動向http://bank.example/withdraw?account=xiaoming&amount=10000&for=hacker
發(fā)出一次HTTP請求。bank.example就會收到包含受害者登錄信息的一次跨域請求。
2.POST類型的CSRF
這種類型的CSRF利用起來通常使用的是一個自動提交的表單,如:
<form action="http://bank.example/withdraw" method=POST> <input type="hidden" name="account" value="xiaoming" /> <input type="hidden" name="amount" value="10000" /> <input type="hidden" name="for" value="hacker" /> </form> <script> document.forms[0].submit(); </script>
訪問該頁面后,表單會自動提交,相當于模擬用戶完成了一次POST操作。
3.鏈接類型的CSRF
鏈接類型的CSRF并不常見,比起其他兩種用戶打開頁面就中招的情況,這種需要用戶點擊鏈接才會觸發(fā)。這種類型通常是在論壇中發(fā)布的圖片中嵌入惡意鏈接,或者以廣告的形式誘導用戶中招,攻擊者通常會以比較夸張的詞語誘騙用戶點擊,例如:
<a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" taget="_blank"> 重磅消息!! <a/>
由于之前用戶登錄了信任的網(wǎng)站A,并且保存登錄狀態(tài),只要用戶主動訪問上面的這個PHP頁面,則表示攻擊成功。
CSRF的特點
攻擊一般發(fā)起在第三方網(wǎng)站,而不是被攻擊的網(wǎng)站。被攻擊的網(wǎng)站無法防止攻擊發(fā)生。
攻擊利用受害者在被攻擊網(wǎng)站的登錄憑證,冒充受害者提交操作;而不是直接竊取數(shù)據(jù)。
整個過程攻擊者并不能獲取到受害者的登錄憑證,僅僅是“冒用”。
跨站請求可以用各種方式:圖片URL、超鏈接、CORS、Form提交等等。部分請求方式可以直接嵌入在第三方論壇、文章中,難以進行追蹤。
CSRF通常是跨域的,因為外域通常更容易被攻擊者掌控。但是如果本域下有容易被利用的功能,比如可以發(fā)圖和鏈接的論壇和評論區(qū),攻擊可以直接在本域下進行,而且這種攻擊更加危險。
防護策略
黑客只能借助受害者的cookie
騙取服務器的信任,但是黑客并不能憑借拿到**「cookie」**,也看不到 **「cookie」的內(nèi)容。另外,對于服務器返回的結(jié)果,由于瀏覽器「同源策略」**的限制,黑客也無法進行解析。
這就告訴我們,我們要保護的對象是那些可以直接產(chǎn)生數(shù)據(jù)改變的服務,而對于讀取數(shù)據(jù)的服務,則不需要進行
CSRF
的保護。而保護的關(guān)鍵,是 「在請求中放入黑客所不能偽造的信息」
同源檢測
既然CSRF大多來自第三方網(wǎng)站,那么我們就直接禁止外域(或者不受信任的域名)對我們發(fā)起請求。
那么問題來了,我們?nèi)绾闻袛嗾埱笫欠駚碜酝庥蚰兀?/p>
在HTTP協(xié)議中,每一個異步請求都會攜帶兩個Header,用于標記來源域名:
Origin Header
Referer Header
這兩個Header在瀏覽器發(fā)起請求時,大多數(shù)情況會自動帶上,并且不能由前端自定義內(nèi)容。 服務器可以通過解析這兩個Header中的域名,確定請求的來源域。
使用Origin Header確定來源域名
在部分與CSRF有關(guān)的請求中,請求的Header中會攜帶Origin字段。字段內(nèi)包含請求的域名(不包含path及query)。
如果Origin存在,那么直接使用Origin中的字段確認來源域名就可以。
但是Origin在以下兩種情況下并不存在:
IE11同源策略: IE 11 不會在跨站CORS請求上添加Origin標頭,Referer頭將仍然是唯一的標識。最根本原因是因為IE 11對同源的定義和其他瀏覽器有不同,有兩個主要的區(qū)別,可以參考MDN Same-origin_policy#IE_Exceptions
302重定向: 在302重定向之后Origin不包含在重定向的請求中,因為Origin可能會被認為是其他來源的敏感信息。對于302重定向的情況來說都是定向到新的服務器上的URL,因此瀏覽器不想將Origin泄漏到新的服務器上。
使用Referer Header確定來源域名
根據(jù)HTTP協(xié)議,在HTTP頭中有一個字段叫Referer,記錄了該HTTP請求的來源地址。 對于Ajax請求,圖片和script等資源請求,Referer為發(fā)起請求的頁面地址。對于頁面跳轉(zhuǎn),Referer為打開頁面歷史記錄的前一個頁面地址。因此我們使用Referer中鏈接的Origin部分可以得知請求的來源域名。
這種方法并非萬無一失,Referer的值是由瀏覽器提供的,雖然HTTP協(xié)議上有明確的要求,但是每個瀏覽器對于Referer的具體實現(xiàn)可能有差別,并不能保證瀏覽器自身沒有安全漏洞。使用驗證 Referer 值的方法,就是把安全性都依賴于第三方(即瀏覽器)來保障,從理論上來講,這樣并不是很安全。在部分情況下,攻擊者可以隱藏,甚至修改自己請求的Referer。
2014年,W3C的Web應用安全工作組發(fā)布了Referrer Policy草案,對瀏覽器該如何發(fā)送Referer做了詳細的規(guī)定。截止現(xiàn)在新版瀏覽器大部分已經(jīng)支持了這份草案,我們終于可以靈活地控制自己網(wǎng)站的Referer策略了。新版的Referrer Policy規(guī)定了五種Referer策略:No Referrer、No Referrer When Downgrade、Origin Only、Origin When Cross-origin、和 Unsafe URL。之前就存在的三種策略:never、default和always,在新標準里換了個名稱。他們的對應關(guān)系如下:
策略名稱 | 屬性值(新) | 屬性值(舊) |
---|---|---|
No Referrer | no-Referrer | never |
No Referrer When Downgrade | no-Referrer-when-downgrade | default |
Origin Only | (same or strict) origin | origin |
Origin When Cross Origin | (strict) origin-when-crossorigin | - |
Unsafe URL | unsafe-url | always |
根據(jù)上面的表格因此需要把Referrer Policy的策略設置成same-origin,對于同源的鏈接和引用,會發(fā)送Referer,referer值為Host不帶Path;跨域訪問則不攜帶Referer。例如:aaa.com
引用bbb.com
的資源,不會發(fā)送Referer。
設置Referrer Policy的方法有三種:
在CSP設置
頁面頭部增加meta標簽
a標簽增加referrerpolicy屬性
上面說的這些比較多,但我們可以知道一個問題:攻擊者可以在自己的請求中隱藏Referer。如果攻擊者將自己的請求這樣填寫:
<img src="http://bank.example/withdraw?amount=10000&for=hacker" referrerpolicy="no-referrer">
那么這個請求發(fā)起的攻擊將不攜帶Referer。
另外在以下情況下Referer沒有或者不可信:
1.IE6、7下使用window.location.href=url進行界面的跳轉(zhuǎn),會丟失Referer。
2.IE6、7下使用window.open,也會缺失Referer。
3.HTTPS頁面跳轉(zhuǎn)到HTTP頁面,所有瀏覽器Referer都丟失。
4.點擊Flash上到達另外一個網(wǎng)站的時候,Referer的情況就比較雜亂,不太可信。
無法確認來源域名情況
當Origin和Referer頭文件不存在時該怎么辦?如果Origin和Referer都不存在,建議直接進行阻止,特別是如果您沒有使用隨機CSRF Token(參考下方)作為第二次檢查。
如何阻止外域請求
通過Header的驗證,我們可以知道發(fā)起請求的來源域名,這些來源域名可能是網(wǎng)站本域,或者子域名,或者有授權(quán)的第三方域名,又或者來自不可信的未知域名。
我們已經(jīng)知道了請求域名是否是來自不可信的域名,我們直接阻止掉這些的請求,就能防御CSRF攻擊了嗎?
且慢!當一個請求是頁面請求(比如網(wǎng)站的主頁),而來源是搜索引擎的鏈接(例如百度的搜索結(jié)果),也會被當成疑似CSRF攻擊。所以在判斷的時候需要過濾掉頁面請求情況,通常Header符合以下情況:
Accept: text/html Method: GET
但相應的,頁面請求就暴露在了CSRF的攻擊范圍之中。如果你的網(wǎng)站中,在頁面的GET請求中對當前用戶做了什么操作的話,防范就失效了。
例如,下面的頁面請求:
GET https://example.com/addComment?comment=XXX&dest=orderId
注:這種嚴格來說并不一定存在CSRF攻擊的風險,但仍然有很多網(wǎng)站經(jīng)常把主文檔GET請求掛上參數(shù)來實現(xiàn)產(chǎn)品功能,但是這樣做對于自身來說是存在安全風險的。
另外,前面說過,CSRF大多數(shù)情況下來自第三方域名,但并不能排除本域發(fā)起。如果攻擊者有權(quán)限在本域發(fā)布評論(含鏈接、圖片等,統(tǒng)稱UGC),那么它可以直接在本域發(fā)起攻擊,這種情況下同源策略無法達到防護的作用。
綜上所述:同源驗證是一個相對簡單的防范方法,能夠防范絕大多數(shù)的CSRF攻擊。但這并不是萬無一失的,對于安全性要求較高,或者有較多用戶輸入內(nèi)容的網(wǎng)站,我們就要對關(guān)鍵的接口做額外的防護措施。
CSRF Token
前面講到CSRF的另一個特征是,攻擊者無法直接竊取到用戶的信息(Cookie,Header,網(wǎng)站內(nèi)容等),僅僅是冒用Cookie中的信息。
而CSRF攻擊之所以能夠成功,是因為服務器誤把攻擊者發(fā)送的請求當成了用戶自己的請求。那么我們可以要求所有的用戶請求都攜帶一個CSRF攻擊者無法獲取到的Token。服務器通過校驗請求是否攜帶正確的Token,來把正常的請求和攻擊的請求區(qū)分開,也可以防范CSRF的攻擊。
利用Cookie的SameSite屬性
SameSite
可以設置為三個值,Strict
、Lax
和None
。
在Strict
模式下,瀏覽器完全禁止第三方請求攜帶Cookie。比如請求sanyuan.com
網(wǎng)站只能在sanyuan.com
域名當中請求才能攜帶 Cookie,在其他網(wǎng)站請求都不能。
在Lax
模式,就寬松一點了,但是只能在 get 方法提交表單
況或者a 標簽發(fā)送 get 請求
的情況下可以攜帶 Cookie,其他情況均不能。
在None模式下,Cookie將在所有上下文中發(fā)送,即允許跨域發(fā)送。
讀到這里,這篇“瀏覽器的面試題有哪些”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領(lǐng)會,如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。