摘要:前言最近在業(yè)務(wù)代碼中深受跨域問題困擾,因此特別寫一篇博客來(lái)記錄一下自己對(duì)跨域的理解以及使用到的參考資料。內(nèi)嵌式跨域通常也是允許的。而我使用時(shí)因?yàn)檫@個(gè)響應(yīng)報(bào)文最后被認(rèn)為是跨域問題,無(wú)法從中獲得的狀態(tài)碼。它代表服務(wù)器支持跨域時(shí)攜帶認(rèn)證信息。
前言
最近在業(yè)務(wù)代碼中深受跨域問題困擾,因此特別寫一篇博客來(lái)記錄一下自己對(duì)跨域的理解以及使用到的參考資料。本文的項(xiàng)目背景基于vue+vuex+axios+springboot。涉及以下內(nèi)容:
何為跨域
HTTP跨域的請(qǐng)求究竟長(zhǎng)啥樣,里面的參數(shù)分別代表什么意思
SpringBoot配置跨域請(qǐng)求
如果對(duì)跨域有所了解的盆友可以直接跳到SpringBoot配置部分查看具體配置,或者是參考文章末尾Spring官網(wǎng)對(duì)CORS配置的博客鏈接。
什么是跨域跨域是指當(dāng)一個(gè)資源從與該資源本身所在的服務(wù)器不同的域或端口請(qǐng)求一個(gè)資源時(shí),資源會(huì)發(fā)起一個(gè)跨域 HTTP 請(qǐng)求。這里盜用MDN上的一張圖:
當(dāng)一個(gè)域名向另一個(gè)不同的域名發(fā)起請(qǐng)求時(shí),這時(shí)就產(chǎn)生了跨域問題。
那么為什么會(huì)出現(xiàn)跨域這樣的概念呢?這就要提到之前規(guī)定的same origin policy。以下是引自維基百科的對(duì)于同源政策的描述:
An origin is defined by the scheme, host, and port of a URL. Generally speaking, documents retrieved from distinct origins are isolated from each other. For example, if a document retrieved from http://example.com/doc.html tries to access the DOM of a document retrieved from https://example.com/target.html, the user agent will disallow access because the origin of the first document, (http, example.com, 80), does not match the origin of the second document (https, example.com, 443).
總而言之,同源就是指擁有同樣的schema,主機(jī)和端口號(hào)的URL,不滿足以上三點(diǎn)的任何一點(diǎn)都代表著這兩個(gè)URL非同源,它們之間的相互訪問就會(huì)產(chǎn)生跨域問題。
這里再借用MDN上的URL是否同源的例子:
而在HTTP訪問中,又有了些許的變化。比如我們通常會(huì)從CDN上獲取CSS,JS等靜態(tài)資源,而這些靜態(tài)資源的域名和當(dāng)前的域并不同源,但是HTTP允許這樣的跨域訪問。因此,我們可以將HTTP上的跨域分為三類:
通常允許跨源寫入。比如鏈接,重定向和表單提交。某些特殊的HTTP請(qǐng)求可能需要預(yù)檢(preflight),后面將會(huì)詳細(xì)介紹這個(gè)詞。
內(nèi)嵌式跨域通常也是允許的。比如獲得CSS文件,標(biāo)簽引入另一個(gè)源的圖片
通常不允許跨源讀取,但讀訪問通常通過(guò)嵌入泄露。例如,您可以讀取嵌入式圖像的寬度和高度,以及嵌入式腳本的操作。前端可以通過(guò)嵌入式跨域變相實(shí)現(xiàn)跨域讀取。前端跨域的方法非常多,不過(guò)不是本文的重點(diǎn),所以不詳細(xì)描述。
為什么會(huì)出現(xiàn)同源政策這里簡(jiǎn)單介紹一下有名的CSFR攻擊來(lái)說(shuō)明同源政策的目的。
這里引用維基百科對(duì)跨站請(qǐng)求攻擊的解釋:
跨站請(qǐng)求攻擊,簡(jiǎn)單地說(shuō),是攻擊者通過(guò)一些技術(shù)手段欺騙用戶的瀏覽器去訪問一個(gè)自己曾經(jīng)認(rèn)證過(guò)的網(wǎng)站并執(zhí)行一些操作(如發(fā)郵件,發(fā)消息,甚至財(cái)產(chǎn)操作如轉(zhuǎn)賬和購(gòu)買商品)。由于瀏覽器曾經(jīng)認(rèn)證過(guò),所以被訪問的網(wǎng)站會(huì)認(rèn)為是真正的用戶操作而去執(zhí)行。這利用了web中用戶身份驗(yàn)證的一個(gè)漏洞:簡(jiǎn)單的身份驗(yàn)證只能保證請(qǐng)求發(fā)自某個(gè)用戶的瀏覽器,卻不能保證請(qǐng)求本身是用戶自愿發(fā)出的。
引用網(wǎng)上的一張圖片:
簡(jiǎn)單的解釋一下跨站請(qǐng)求的實(shí)現(xiàn),維基百科上也有非常詳細(xì)的例子。
假設(shè)現(xiàn)在用戶在網(wǎng)頁(yè)上進(jìn)行轉(zhuǎn)賬,只有通過(guò)身份驗(yàn)證的用戶才可以進(jìn)行該操作。假設(shè)服務(wù)器是通過(guò)這樣的一個(gè)URL http://bank.example/withdraw?account=a&amount=1000000&for=b實(shí)現(xiàn)a向b轉(zhuǎn)賬100000塊的業(yè)務(wù)。因?yàn)樵撜?qǐng)求會(huì)攜帶用戶的身份認(rèn)證信息,因此它能夠通過(guò)服務(wù)器的認(rèn)證并實(shí)現(xiàn)操作。但是這時(shí)惡意用戶c希望用這樣一個(gè)形式的URLhttp://bank.example/withdraw?account=a&amount=1000000&for=c
因?yàn)閏自己并不具有a的session,因此他會(huì)通過(guò)別的方式誘惑a用戶執(zhí)行這個(gè)操作。比如它會(huì)通過(guò)發(fā)送惡意郵件的方式騙a點(diǎn)擊上面的超鏈接。a如果此時(shí)并沒有退出bank.example的登錄即其session信息未被清空,那么a將成功通過(guò)服務(wù)器的認(rèn)證進(jìn)行轉(zhuǎn)賬,實(shí)現(xiàn)了c的“心愿”。其它的還有諸如在用戶進(jìn)入惡意網(wǎng)站后利用js腳本自動(dòng)提交表單向bank.example發(fā)出帶有a的session的post請(qǐng)求等等。
同源政策將會(huì)確保網(wǎng)站a拒絕來(lái)自網(wǎng)站b的請(qǐng)求。
那為什么又需要跨域當(dāng)前端框架興起之后,前后端徹底分離的開發(fā)方式漸漸流行。前端和后端往往部署在不同的域名之上。前端通過(guò)訪問后端的API獲取數(shù)據(jù),渲染前端界面,甚至進(jìn)行路由跳轉(zhuǎn)。這通常意味著前后端會(huì)出現(xiàn)不同源的問題。因?yàn)榧词共渴鹪谕慌_(tái)主機(jī)上,二者也屬于不同的端口。那么我們就需要某種策略使得跨域請(qǐng)求能夠通過(guò)。支持跨域的方式有很多,下文主要介紹后端Spring Boot配置支持跨域訪問。
跨域訪問的HTTP報(bào)文之前配置Spring Boot跨域的時(shí)候,我都是直接從網(wǎng)上抄一段這樣的代碼:
@Configuration public class WebConfig extends WebMvcConfigurationSupport { ...... public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/***") .allowedHeaders("**") .allowedMethods("GET", "POST") .allowedOrigins("*"); } }
一開始在開發(fā)過(guò)程中,這段代碼并沒有問題。但是在試圖接入登錄業(yè)務(wù)之后出現(xiàn)了問題。因?yàn)椴捎肈ubbo進(jìn)行微服務(wù)開始,我們決定將登錄作為一個(gè)多帶帶的業(yè)務(wù)獨(dú)立部署在另一個(gè)容器中。登錄業(yè)務(wù)的基本流程是訪問登錄容器,登錄成功后返回一個(gè)token存儲(chǔ)在服務(wù)器的localStorage中。之后每次訪問別的服務(wù)時(shí)都會(huì)在header中攜帶這個(gè)token,服務(wù)利用攔截器對(duì)token進(jìn)行解析,判斷其是否合法以及是否生效,如果合法則將解析結(jié)果放入request中傳遞給后面的Controller。
在上面這個(gè)配置的基礎(chǔ)上出現(xiàn)了幾個(gè)問題:
在發(fā)送請(qǐng)求前,會(huì)發(fā)送preflight的OPTION請(qǐng)求來(lái)判斷服務(wù)器是否支持該域的跨域請(qǐng)求以及支持的跨域方法,但是該配置并不支持跨域的OPTION請(qǐng)求,從而導(dǎo)致OPTION方法無(wú)法通過(guò),進(jìn)而無(wú)法發(fā)送真正的GET或是POST請(qǐng)求
針對(duì)1中的問題開放OPTION請(qǐng)求之后,如果不進(jìn)行認(rèn)證就去訪問需要認(rèn)證的業(yè)務(wù),雖然獲得了401的狀態(tài)碼,但是會(huì)出現(xiàn)跨域請(qǐng)求失敗的問題。如果你去查看該請(qǐng)求的響應(yīng)頭,會(huì)發(fā)現(xiàn)響應(yīng)header中確實(shí)沒有access-control-allow-origin字段!也就是說(shuō)響應(yīng)被攔截器攔截,甚至沒有進(jìn)入跨域訪問的響應(yīng)邏輯。而我使用axios時(shí)因?yàn)檫@個(gè)響應(yīng)報(bào)文最后被認(rèn)為是跨域問題,無(wú)法從error中獲得401的狀態(tài)碼。
總之,因?yàn)椴恢酪粋€(gè)真正的跨域請(qǐng)求的報(bào)文應(yīng)該是什么樣子的,所以盲目的折騰了半天,甚至沒能將問題定位到后端的跨域配置。所以,現(xiàn)在來(lái)看一下真正的跨域請(qǐng)求報(bào)文究竟是什么樣子的,來(lái)了解一下跨域的原理。
跨域報(bào)文 preflight在次之前,先了解一下preflight。
我們?nèi)ゲ榭礊g覽器發(fā)出的跨域請(qǐng)求時(shí),經(jīng)常會(huì)看到一個(gè)OPTION報(bào)文,它的url和真正的GET或是POST請(qǐng)求的URL相同。這個(gè)OPTION請(qǐng)求就是傳說(shuō)中的preflight請(qǐng)求。preflight請(qǐng)求是為了詢問服務(wù)器該跨域請(qǐng)求是否可以被識(shí)別或是被允許。
preflight報(bào)文通常長(zhǎng)成這樣:
OPTIONS /resource/foo Access-Control-Request-Method: DELETE Access-Control-Request-Headers: origin, x-requested-with Origin: https://foo.bar.org
如果允許來(lái)自該IP的跨域訪問,服務(wù)器會(huì)用Access-Control-Allow-Origin頭字段說(shuō)明允,并在Access-Control-Allow-Methods指明允許的方法。preflight響應(yīng)報(bào)文通常長(zhǎng)成這樣:
HTTP/1.1 200 OK Content-Length: 0 Connection: keep-alive Access-Control-Allow-Origin: https://foo.bar.org Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE Access-Control-Max-Age: 86400
這里Access-Control—Max-Age指定了在86400s內(nèi)無(wú)需為該URL發(fā)送preflight請(qǐng)求。
至于為何需要preflight請(qǐng)參考reference中的文章。
并不是所有的請(qǐng)求都需要發(fā)送preflight請(qǐng)求,服務(wù)器面對(duì)簡(jiǎn)單請(qǐng)求會(huì)直接返回Access-Control-Allow-Origin響應(yīng)頭來(lái)說(shuō)明它的跨域訪問是否通過(guò),如果通過(guò),則會(huì)在響應(yīng)體中直接攜帶數(shù)據(jù)。請(qǐng)求和響應(yīng)報(bào)文如下:
GET /resources/public-data/ HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Referer: http://foo.example/examples/access-control/simpleXSInvocation.html Origin: http://foo.example HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 00:23:53 GMT Server: Apache/2.0.61 Access-Control-Allow-Origin: * Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Transfer-Encoding: chunked Content-Type: application/xml [xml]
服務(wù)器會(huì)檢查origin字段的URL是否允許跨域請(qǐng)求??梢钥吹皆摲?wù)器允許來(lái)自一切IP的跨域訪問,因?yàn)樗祷氐捻憫?yīng)頭為Access-Control-Allow-Origin: *。
你會(huì)發(fā)現(xiàn),這里的請(qǐng)求和一般的HTTP請(qǐng)求并沒有太大的差別。
那么,什么是簡(jiǎn)單請(qǐng)求呢?
滿足以上要求的則為簡(jiǎn)單請(qǐng)求。而通常前后端分離的服務(wù)之間會(huì)通過(guò)json形式的數(shù)據(jù)進(jìn)行溝通,即content-type為application/json。而這種形式不符合簡(jiǎn)單請(qǐng)求的定義,因此需要使用option請(qǐng)求進(jìn)行預(yù)檢。
復(fù)雜請(qǐng)求的預(yù)檢請(qǐng)求報(bào)文如下:
OPTIONS /resources/post-here/ HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Origin: http://foo.example Access-Control-Request-Method: POST Access-Control-Request-Headers: X-PINGOTHER, Content-Type
請(qǐng)求頭中有兩個(gè)字段比較特殊。Access-Control-Request-Method說(shuō)明真正的跨域請(qǐng)求的方法,這里是POST方法,而Access-Control-Request-Headers則說(shuō)明請(qǐng)求頭中包含哪些非簡(jiǎn)單字段。服務(wù)器端會(huì)根據(jù)自身的配置查看是否支持包含這些非簡(jiǎn)單字段的請(qǐng)求,如果不包含,則該跨域請(qǐng)求會(huì)被拒絕。
預(yù)檢響應(yīng)報(bào)文如下:
HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2.0.61 (Unix) Access-Control-Allow-Origin: http://foo.example Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-PINGOTHER, Content-Type Access-Control-Max-Age: 86400 Vary: Accept-Encoding, Origin Content-Encoding: gzip Content-Length: 0 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain
這里Access-Control-Allow-Headers說(shuō)明了服務(wù)器支持的跨域請(qǐng)求的這些字段。
之后服務(wù)器會(huì)發(fā)送真實(shí)的請(qǐng)求,服務(wù)器會(huì)對(duì)之響應(yīng),其響應(yīng)頭中會(huì)包含Access-Control-Allow-Origin字段。
身份認(rèn)證 Spring-Boot 配置現(xiàn)在我們?cè)賮?lái)看一下之前的配置:
@Configuration public class WebConfig extends WebMvcConfigurationSupport { ...... public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/***")//對(duì)/api/**進(jìn)行跨域配置 .allowedHeaders("**")//允許所有的非簡(jiǎn)單請(qǐng)求頭 .allowedMethods("GET", "OPTIONS", "POST") //允許三種方法 .allowedOrigins("*");//允許來(lái)自所有域的請(qǐng)求 } }
當(dāng)然這種全部符合的通配符并不是一個(gè)很好的選擇,我們應(yīng)當(dāng)限制跨域請(qǐng)求的形式,從而拒絕不符合要求的請(qǐng)求。
第二種配置是采用Filter的形式進(jìn)行配置。位于最前面的Filter會(huì)在請(qǐng)求進(jìn)入任何其它位置之前對(duì)其進(jìn)行處理。
@Configuration public class MyConfiguration { @Bean public FilterRegistrationBean corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("http://domain1.com"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); bean.setOrder(0); return bean; } }
這里跟上面的配置意思基本相同,區(qū)別在于這里引入了setAllowCredential配置。它代表服務(wù)器支持跨域時(shí)攜帶認(rèn)證信息。需要注意的是,如果開啟這個(gè)配置,則allowedOrigins不可以為*。
Referencespringboot設(shè)置cors跨域請(qǐng)求的兩種方式
spring官網(wǎng)-設(shè)置允許跨域請(qǐng)求
MDN Http 控制訪問
MDN Same Origin Policy
What"s the motivation of preflight
若想了解更多技術(shù)資訊、面試教程以及互聯(lián)網(wǎng)公司的內(nèi)推信息,歡迎關(guān)注我的公眾號(hào)!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/69504.html
摘要:基礎(chǔ)深度學(xué)習(xí)概念備忘錄后端掘金基礎(chǔ)深度學(xué)習(xí)概念備忘錄翻譯自。否則,試想在你捧著某出版社剛剛翻譯出來(lái)的高效編程苦規(guī)范及相關(guān)文檔前端掘金官方規(guī)范歲程序員的獨(dú)家面試經(jīng)歷閱讀掘金創(chuàng)業(yè)失敗后,在找工作。 基礎(chǔ)深度學(xué)習(xí)概念備忘錄 - 后端 - 掘金基礎(chǔ)深度學(xué)習(xí)概念備忘錄翻譯自DeepLearning Cheat Sheet。筆者還是菜鳥一枚,若有謬誤請(qǐng)多多賜教,另外如果希望了解更多機(jī)器學(xué)習(xí)&深度學(xué)...
摘要:希望幫助更多的前端愛好者學(xué)習(xí)。前端開發(fā)者指南作者科迪林黎,由前端大師傾情贊助。翻譯最佳實(shí)踐譯者張捷滬江前端開發(fā)工程師當(dāng)你問起有關(guān)與時(shí),老司機(jī)們首先就會(huì)告訴你其實(shí)是個(gè)沒有網(wǎng)絡(luò)請(qǐng)求功能的庫(kù)。 前端基礎(chǔ)面試題(JS部分) 前端基礎(chǔ)面試題(JS部分) 學(xué)習(xí) React.js 比你想象的要簡(jiǎn)單 原文地址:Learning React.js is easier than you think 原文作...
摘要:忍者級(jí)別的函數(shù)操作對(duì)于什么是匿名函數(shù),這里就不做過(guò)多介紹了。我們需要知道的是,對(duì)于而言,匿名函數(shù)是一個(gè)很重要且具有邏輯性的特性。通常,匿名函數(shù)的使用情況是創(chuàng)建一個(gè)供以后使用的函數(shù)。 JS 中的遞歸 遞歸, 遞歸基礎(chǔ), 斐波那契數(shù)列, 使用遞歸方式深拷貝, 自定義事件添加 這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機(jī)制,如果...
摘要:用于告知瀏覽器可以將預(yù)先檢查請(qǐng)求返回結(jié)果緩存的時(shí)間,在緩存有效期內(nèi),瀏覽器會(huì)使用緩存的預(yù)先檢查結(jié)果判斷是否發(fā)送跨域請(qǐng)求。 跨域,老生常談的問題 簡(jiǎn)述 作為一只前端菜鳥,跨域方面只懂得JSONP和CORS,并未曾深入了解。但隨著春招越來(lái)越近,就算是菜鳥也要猛振翅膀。近幾日仔細(xì)研究了跨域問題,寫下這篇文章,希望對(duì)開發(fā)者們有所幫助。在讀本文前,希望您對(duì)以下知識(shí)略有了解。 瀏覽器同源策略 n...
閱讀 2377·2021-11-23 10:09
閱讀 2927·2021-10-12 10:11
閱讀 2621·2021-09-29 09:35
閱讀 1366·2019-08-30 15:53
閱讀 2292·2019-08-30 11:15
閱讀 2940·2019-08-29 13:01
閱讀 2321·2019-08-28 18:15
閱讀 3397·2019-08-26 12:13