web开发的跨域问题详解
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
本文由云+社區(qū)發(fā)表
做過 web 開發(fā)的同學(xué),應(yīng)該都遇到過跨域的問題,當(dāng)我們從一個(gè)域名向另一個(gè)域名發(fā)送 Ajax 請(qǐng)求的時(shí)候,打開瀏覽器控制臺(tái)就會(huì)看到跨域錯(cuò)誤,今天我們就來聊聊跨域的問題。
1. 瀏覽器的同源策略
同源的定義是:如果兩個(gè)頁面的***協(xié)議***,*端口*(如果有指定)和***域名*都相同,則兩個(gè)頁面具有相同的源**。同源策略限制了從同一個(gè)源加載的文檔或腳本如何與來自另一個(gè)源的資源進(jìn)行交互。這是一個(gè)用于隔離潛在惡意文件的重要安全機(jī)制。
2. 跨域錯(cuò)誤信息產(chǎn)生的原因
為了說明問題,我們可以做如下實(shí)驗(yàn),我們?cè)诒镜卮罱碎_發(fā)環(huán)境, 由客戶端 http://localhost:3001 向服務(wù)器 http://localhost:3000 發(fā)送兩個(gè)請(qǐng)求,一個(gè)使用 javascript 異步請(qǐng)求數(shù)據(jù),另一個(gè)使用 img 標(biāo)簽請(qǐng)求數(shù)據(jù),服務(wù)器收到請(qǐng)求后,打印接收到請(qǐng)求的日志,如下圖所示:
客戶端發(fā)送兩個(gè)請(qǐng)求
服務(wù)端打印日志并處理請(qǐng)求
代開客戶端瀏覽器的控制臺(tái),可以看到發(fā)出了兩個(gè)請(qǐng)求,并且都收到了狀態(tài)碼為 200 的響應(yīng),同時(shí)控制臺(tái)報(bào)了一個(gè)錯(cuò)誤,即 xhr 請(qǐng)求報(bào)錯(cuò)。由此我們可以知道,之所以產(chǎn)生跨域錯(cuò)誤信息,原因有以下三條:
- 瀏覽器端的限制(服務(wù)端收到了請(qǐng)求并正確返回)
- 發(fā)送的是 XMLHttpRequest 請(qǐng)求(使用 img 標(biāo)簽發(fā)送的請(qǐng)求為 json 類型,并不會(huì)報(bào)錯(cuò))
- 請(qǐng)求了不同域的資源
只有同時(shí)滿足了這三個(gè)條件,瀏覽器才會(huì)產(chǎn)生跨域錯(cuò)誤。
3. 解決跨域的思路
既然我們知道了跨域錯(cuò)誤產(chǎn)生的原因,那么解決思路就很直觀了,針對(duì)出錯(cuò)的三個(gè)原因進(jìn)行相應(yīng)的處理即可,相應(yīng)的解決思路也有三個(gè)方向:
- 打破瀏覽器的限制
- 不發(fā)送 XHR 請(qǐng)求
- 解決跨域
下文將分別進(jìn)行闡述。
3.1 打破瀏覽器的限制
由上面分析結(jié)論可知,之所以出現(xiàn)跨域的錯(cuò)誤,實(shí)際上是客戶端瀏覽器所做的限制,服務(wù)器并未進(jìn)行限制,因此我們可以通過設(shè)置瀏覽器,使其不進(jìn)行跨域檢查。實(shí)際上瀏覽器也提供了對(duì)應(yīng)的設(shè)置選項(xiàng)。
以 MacOS 下的 Chrome 瀏覽器為例,在終端中使用命令
open -n /Applications/Google\ Chrome.app/ --args --disable-web-security --user-data-dir=/Users/your-computer-account/MyChromeDevUserData/打開瀏覽器,即可禁用 Chrome 瀏覽器的安全檢查功能,同時(shí)也會(huì)禁用跨域安全檢查功能,這樣再次拿前面的例子進(jìn)行測(cè)試,發(fā)現(xiàn)此時(shí)不會(huì)報(bào)錯(cuò),同時(shí)也可以正確拿到服務(wù)端返回的數(shù)據(jù)。
禁用瀏覽器安全檢查功能
這種方式雖然可以實(shí)現(xiàn)跨域,但是需要每個(gè)用戶都對(duì)瀏覽器進(jìn)行設(shè)置,同時(shí)可能導(dǎo)致潛在的安全隱患,正常情況下不實(shí)用。但這個(gè)例子充分說明了,跨域錯(cuò)誤是前端瀏覽器所做的限制,與后臺(tái)服務(wù)無關(guān)。
3.2 JSONP實(shí)現(xiàn)跨域
根據(jù)思路2,既然跨域問題產(chǎn)生的原因是因?yàn)榭蛻舳税l(fā)送了 Ajax 請(qǐng)求,那么我們打破這個(gè)條件即可。具體實(shí)現(xiàn)方式就是使用 JSONP 來進(jìn)行跨域請(qǐng)求。
JSONP,是 JSON with Padding 的簡(jiǎn)稱,它是 json 的一種補(bǔ)充使用方式,利用 script 標(biāo)簽來解決跨域問題。JSONP 是非官方協(xié)議,他只是前后端一個(gè)約定,如果請(qǐng)求參數(shù)帶有約定的參數(shù),則后臺(tái)返回 javascript 代碼而非 json 數(shù)據(jù),返回代碼是函數(shù)調(diào)用形式,函數(shù)名即約定值,函數(shù)參數(shù)即要返回的數(shù)據(jù)。JSONP 的實(shí)現(xiàn)原理如下圖所示:
JSONP實(shí)現(xiàn)原理
首先在客戶端 js 中定義一個(gè)函數(shù)(假設(shè)名為 handler),然后動(dòng)態(tài)創(chuàng)建一個(gè) script 標(biāo)簽插入頁面中,script 標(biāo)簽的 src 屬性即要調(diào)用的地址,同時(shí),在調(diào)用的 url 中加入一個(gè)服務(wù)端約定的參數(shù)(假設(shè)名為 callback,參數(shù)值為已定義的函數(shù)名 handler),服務(wù)端收到請(qǐng)求,如果發(fā)現(xiàn)請(qǐng)求的 url 中帶有約定的參數(shù),那么就返回一段函數(shù)調(diào)用形式的 javascript 代碼,該段代碼的函數(shù)名即為 callback 參數(shù)的值 handler,函數(shù)的參數(shù)即為待返回的數(shù)據(jù)。這樣,客戶端拿到返回結(jié)果后就會(huì)執(zhí)行 handler 函數(shù),對(duì)返回的數(shù)據(jù)進(jìn)行處理。
我們使用 jquery 向服務(wù)端發(fā)送一個(gè) JSONP 格式的請(qǐng)求,從瀏覽器控制臺(tái)可以看到請(qǐng)求和對(duì)應(yīng)的響應(yīng),如下圖所示:
JSONP請(qǐng)求
JSONP請(qǐng)求的響應(yīng)
由上圖可以看到,發(fā)送JSONP請(qǐng)求時(shí),請(qǐng)求的 Type 為 script 類型而非 xhr 類型,這樣就打破了跨域報(bào)錯(cuò)的三個(gè)必要條件,不會(huì)產(chǎn)生跨域錯(cuò)誤,同時(shí)也驗(yàn)證了服務(wù)端返回的數(shù)據(jù)格式為 javascript 代碼調(diào)用的形式,其中 Jquery331045** 這一長串函數(shù)名是 jquery 自動(dòng)生成的。
由于 JSONP 的原理是使用 script 標(biāo)簽來加載數(shù)據(jù),所以它的兼容性很好,但是使用 JSONP 來解決跨域問題存在以下缺陷:
3.3 跨域資源共享CORS
CORS 是一個(gè) W3C 標(biāo)準(zhǔn),全稱是"跨域資源共享"(Cross-origin resource sharing)。它允許瀏覽器向跨源服務(wù)器,發(fā)出 XMLHttpRequest 請(qǐng)求,從而克服了 AJAX 只能同源使用的限制。CORS 基于 http 協(xié)議關(guān)于跨域方面的規(guī)定,使用時(shí),客戶端瀏覽器直接異步請(qǐng)求被調(diào)用端服務(wù)端,在響應(yīng)頭增加響應(yīng)的字段,告訴瀏覽器后臺(tái)允許跨域。
跨域錯(cuò)誤
回到文章開始的這個(gè)跨域錯(cuò)誤信息,可以看到錯(cuò)誤的具體信息是:服務(wù)端沒有設(shè)置Access-Control-Allow-Origin 這個(gè)響應(yīng)頭從而導(dǎo)致報(bào)錯(cuò),通過設(shè)置 Access-Control-Allow-Origin: * 這個(gè)響應(yīng)頭,我們可以解決問題。但是,這種設(shè)置能滿足所有情況嗎? 更進(jìn)一步,使用 CORS 時(shí)瀏覽器如何檢查跨域錯(cuò)誤? 前面我們有講到,雖然瀏覽器報(bào)錯(cuò),但是在這之前服務(wù)端已經(jīng)接受了請(qǐng)求,那么,瀏覽器總是先發(fā)出請(qǐng)求后再進(jìn)行判斷嗎?下面我們一一討論。
3.3.1 瀏覽器如何檢查跨域錯(cuò)誤
瀏覽器檢查跨域錯(cuò)誤的基本原理是:
瀏覽器檢測(cè)到 ajax 請(qǐng)求的域與當(dāng)前域不一致,會(huì)在請(qǐng)求頭中增加 Origin 字段,然后檢查服務(wù)端響應(yīng)頭 Access-Control-Allow-Origin,如果不存在或不匹配,則報(bào)跨域錯(cuò)誤。瀏覽器檢查跨域錯(cuò)誤原理
3.3.2 瀏覽器總是先發(fā)出請(qǐng)求,然后根據(jù)是否有 Access-Control-Allow-Origin 響應(yīng)頭來判斷嗎
答案是,對(duì)于簡(jiǎn)單請(qǐng)求,是;而對(duì)于非簡(jiǎn)單請(qǐng)求,不是。非簡(jiǎn)單請(qǐng)求的情況下,瀏覽器并不是直接請(qǐng)求所需資源,而是會(huì)先發(fā)出一個(gè)預(yù)檢請(qǐng)求,預(yù)檢請(qǐng)求通過后才會(huì)對(duì)所需資源進(jìn)行請(qǐng)求。
非簡(jiǎn)單請(qǐng)求預(yù)檢請(qǐng)求
這里涉及到的簡(jiǎn)單請(qǐng)求和非簡(jiǎn)單請(qǐng)求的概念,那么簡(jiǎn)單請(qǐng)求和非簡(jiǎn)單請(qǐng)求有什么區(qū)別呢?MDN 對(duì)非簡(jiǎn)單請(qǐng)求進(jìn)行了定義,滿足下列條件之一,即為非簡(jiǎn)單請(qǐng)求:
簡(jiǎn)單來說,除了我們平時(shí)使用最多的 GET 和 POST 方法,以及最常使用的 Accept、Accept-Language、Content-Language 和 類型為 application/x-www-form-urlencoded、 multipart/form-data、 text/plain 的 Content-Type 請(qǐng)求頭,其他基本都是非簡(jiǎn)單請(qǐng)求。對(duì)于這些非簡(jiǎn)單請(qǐng)求,瀏覽器會(huì)發(fā)出兩個(gè)請(qǐng)求,第一個(gè)為 OPTIONS 遇見請(qǐng)求,遇見請(qǐng)求的響應(yīng)檢查通過后才會(huì)發(fā)出對(duì)資源的請(qǐng)求。
非簡(jiǎn)單請(qǐng)求過程
生產(chǎn)環(huán)境下,如果需要發(fā)送非簡(jiǎn)單跨域請(qǐng)求,每次兩個(gè)請(qǐng)求會(huì)增加響應(yīng)時(shí)間,為此,W3C 標(biāo)準(zhǔn)中增加了另一個(gè)響應(yīng)頭 Access-Control-Max-Age 參數(shù),該響應(yīng)頭表明了對(duì)于非簡(jiǎn)單請(qǐng)求的預(yù)檢請(qǐng)求瀏覽器的緩存時(shí)間,在緩存有效期內(nèi),非簡(jiǎn)單請(qǐng)求可以不發(fā)送預(yù)檢請(qǐng)求,另外,實(shí)際開發(fā)中,可以在服務(wù)端設(shè)置接收到的請(qǐng)求方法是 OPTIONS 時(shí),直接返回 200,這樣也能加快響應(yīng)。
3.3.3 設(shè)置 Access-Control-Allow-Origin: * 就行嗎
帶cookie的跨域
當(dāng)我們需要發(fā)送帶 cookie 的請(qǐng)求時(shí),Access-Control-Allow-Origin 直接設(shè)置為通配符 * 時(shí)是無法通過瀏覽器的檢查的,此時(shí)該響應(yīng)頭的值必須與發(fā)出請(qǐng)求的域完全匹配才行,另外,還需要設(shè)置 Access-Control-Allow-Credentials 響應(yīng)頭的值為 true,表示支持帶 cookie 的跨域請(qǐng)求。
3.3.4 CORS請(qǐng)求頭和響應(yīng)頭總結(jié)
請(qǐng)求頭:
- Origin: 瀏覽器發(fā)出 Ajax 跨域請(qǐng)求之前會(huì)添加此頭部,值為發(fā)送請(qǐng)求的域
- Access-Control-Request-Method:使用了除 GET、POST 請(qǐng)求方法之外的方法,瀏覽器會(huì)添加此頭部,值為當(dāng)前請(qǐng)求方法
- Access-Control-Request-Headers:使用了自定義頭部或除了Accept、Accept-Language、Content-Language、Content-Type 之外的頭部,瀏覽器會(huì)添加此頭部,值為當(dāng)前的請(qǐng)求方法
響應(yīng)頭:
- Access-Control-Allow-Origin: 表示服務(wù)端允許哪些域請(qǐng)求資源
- Access-Control-Allow-Methods: 當(dāng)客戶端包含 Access-Control-Request-Method 請(qǐng)求頭時(shí),服務(wù)端需要響應(yīng)該頭部,值通常由 Reauest 的 header 中 Access-Control-Request-Method 取得
- Access-Control-Allow-Headers: 當(dāng)客戶端包含 Access-Control-Request-Headers 請(qǐng)求頭時(shí),服務(wù)端需要響應(yīng)該頭部,值通常由 Reauest 的 header 中 Access-Control-Request-Headers 取得
- Access-Control-Expose-Headers: 指出客戶端通過 XHR 對(duì)象的 getResponseHeaders 方法可以獲取的響應(yīng)頭有哪些
- Access-Control-Allow-Credentials: 允許帶 cookie 的跨域請(qǐng)求
- Access-Control-Max-Age: 預(yù)檢請(qǐng)求的緩存時(shí)間
4. 總結(jié)
本文介紹了跨域的原因,重點(diǎn)介紹了使用 JSONP 和 CORS 解決跨域問題的方法。除此之外,實(shí)際開發(fā)中還其他各種解決跨域問題的思路,本質(zhì)上,這些方法都是打破跨域錯(cuò)誤的三個(gè)條件,大家可以自行查資料了解一下。
此文已由作者授權(quán)騰訊云+社區(qū)在各渠道發(fā)布
轉(zhuǎn)載于:https://my.oschina.net/qcloudcommunity/blog/2994843
總結(jié)
以上是生活随笔為你收集整理的web开发的跨域问题详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数组去重一步到位
- 下一篇: 微软为无服务器架构引入新API管理消费层