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