摘要:前言最近在業務代碼中深受跨域問題困擾,因此特別寫一篇博客來記錄一下自己對跨域的理解以及使用到的參考資料。內嵌式跨域通常也是允許的。而我使用時因為這個響應報文最后被認為是跨域問題,無法從中獲得的狀態碼。它代表服務器支持跨域時攜帶認證信息。
前言
最近在業務代碼中深受跨域問題困擾,因此特別寫一篇博客來記錄一下自己對跨域的理解以及使用到的參考資料。本文的項目背景基于vue+vuex+axios+springboot。涉及以下內容:
何為跨域
HTTP跨域的請求究竟長啥樣,里面的參數分別代表什么意思
SpringBoot配置跨域請求
如果對跨域有所了解的盆友可以直接跳到SpringBoot配置部分查看具體配置,或者是參考文章末尾Spring官網對CORS配置的博客鏈接。
什么是跨域跨域是指當一個資源從與該資源本身所在的服務器不同的域或端口請求一個資源時,資源會發起一個跨域 HTTP 請求。這里盜用MDN上的一張圖:
當一個域名向另一個不同的域名發起請求時,這時就產生了跨域問題。
那么為什么會出現跨域這樣的概念呢?這就要提到之前規定的same origin policy。以下是引自維基百科的對于同源政策的描述:
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,主機和端口號的URL,不滿足以上三點的任何一點都代表著這兩個URL非同源,它們之間的相互訪問就會產生跨域問題。
這里再借用MDN上的URL是否同源的例子:
而在HTTP訪問中,又有了些許的變化。比如我們通常會從CDN上獲取CSS,JS等靜態資源,而這些靜態資源的域名和當前的域并不同源,但是HTTP允許這樣的跨域訪問。因此,我們可以將HTTP上的跨域分為三類:
通常允許跨源寫入。比如鏈接,重定向和表單提交。某些特殊的HTTP請求可能需要預檢(preflight),后面將會詳細介紹這個詞。
內嵌式跨域通常也是允許的。比如獲得CSS文件,標簽引入另一個源的圖片
通常不允許跨源讀取,但讀訪問通常通過嵌入泄露。例如,您可以讀取嵌入式圖像的寬度和高度,以及嵌入式腳本的操作。前端可以通過嵌入式跨域變相實現跨域讀取。前端跨域的方法非常多,不過不是本文的重點,所以不詳細描述。
為什么會出現同源政策這里簡單介紹一下有名的CSFR攻擊來說明同源政策的目的。
這里引用維基百科對跨站請求攻擊的解釋:
跨站請求攻擊,簡單地說,是攻擊者通過一些技術手段欺騙用戶的瀏覽器去訪問一個自己曾經認證過的網站并執行一些操作(如發郵件,發消息,甚至財產操作如轉賬和購買商品)。由于瀏覽器曾經認證過,所以被訪問的網站會認為是真正的用戶操作而去執行。這利用了web中用戶身份驗證的一個漏洞:簡單的身份驗證只能保證請求發自某個用戶的瀏覽器,卻不能保證請求本身是用戶自愿發出的。
引用網上的一張圖片:
簡單的解釋一下跨站請求的實現,維基百科上也有非常詳細的例子。
假設現在用戶在網頁上進行轉賬,只有通過身份驗證的用戶才可以進行該操作。假設服務器是通過這樣的一個URL http://bank.example/withdraw?account=a&amount=1000000&for=b實現a向b轉賬100000塊的業務。因為該請求會攜帶用戶的身份認證信息,因此它能夠通過服務器的認證并實現操作。但是這時惡意用戶c希望用這樣一個形式的URLhttp://bank.example/withdraw?account=a&amount=1000000&for=c
因為c自己并不具有a的session,因此他會通過別的方式誘惑a用戶執行這個操作。比如它會通過發送惡意郵件的方式騙a點擊上面的超鏈接。a如果此時并沒有退出bank.example的登錄即其session信息未被清空,那么a將成功通過服務器的認證進行轉賬,實現了c的“心愿”。其它的還有諸如在用戶進入惡意網站后利用js腳本自動提交表單向bank.example發出帶有a的session的post請求等等。
同源政策將會確保網站a拒絕來自網站b的請求。
那為什么又需要跨域當前端框架興起之后,前后端徹底分離的開發方式漸漸流行。前端和后端往往部署在不同的域名之上。前端通過訪問后端的API獲取數據,渲染前端界面,甚至進行路由跳轉。這通常意味著前后端會出現不同源的問題。因為即使部署在同一臺主機上,二者也屬于不同的端口。那么我們就需要某種策略使得跨域請求能夠通過。支持跨域的方式有很多,下文主要介紹后端Spring Boot配置支持跨域訪問。
跨域訪問的HTTP報文之前配置Spring Boot跨域的時候,我都是直接從網上抄一段這樣的代碼:
@Configuration public class WebConfig extends WebMvcConfigurationSupport { ...... public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/***") .allowedHeaders("**") .allowedMethods("GET", "POST") .allowedOrigins("*"); } }
一開始在開發過程中,這段代碼并沒有問題。但是在試圖接入登錄業務之后出現了問題。因為采用Dubbo進行微服務開始,我們決定將登錄作為一個多帶帶的業務獨立部署在另一個容器中。登錄業務的基本流程是訪問登錄容器,登錄成功后返回一個token存儲在服務器的localStorage中。之后每次訪問別的服務時都會在header中攜帶這個token,服務利用攔截器對token進行解析,判斷其是否合法以及是否生效,如果合法則將解析結果放入request中傳遞給后面的Controller。
在上面這個配置的基礎上出現了幾個問題:
在發送請求前,會發送preflight的OPTION請求來判斷服務器是否支持該域的跨域請求以及支持的跨域方法,但是該配置并不支持跨域的OPTION請求,從而導致OPTION方法無法通過,進而無法發送真正的GET或是POST請求
針對1中的問題開放OPTION請求之后,如果不進行認證就去訪問需要認證的業務,雖然獲得了401的狀態碼,但是會出現跨域請求失敗的問題。如果你去查看該請求的響應頭,會發現響應header中確實沒有access-control-allow-origin字段!也就是說響應被攔截器攔截,甚至沒有進入跨域訪問的響應邏輯。而我使用axios時因為這個響應報文最后被認為是跨域問題,無法從error中獲得401的狀態碼。
總之,因為不知道一個真正的跨域請求的報文應該是什么樣子的,所以盲目的折騰了半天,甚至沒能將問題定位到后端的跨域配置。所以,現在來看一下真正的跨域請求報文究竟是什么樣子的,來了解一下跨域的原理。
跨域報文 preflight在次之前,先了解一下preflight。
我們去查看瀏覽器發出的跨域請求時,經常會看到一個OPTION報文,它的url和真正的GET或是POST請求的URL相同。這個OPTION請求就是傳說中的preflight請求。preflight請求是為了詢問服務器該跨域請求是否可以被識別或是被允許。
preflight報文通常長成這樣:
OPTIONS /resource/foo Access-Control-Request-Method: DELETE Access-Control-Request-Headers: origin, x-requested-with Origin: https://foo.bar.org
如果允許來自該IP的跨域訪問,服務器會用Access-Control-Allow-Origin頭字段說明允,并在Access-Control-Allow-Methods指明允許的方法。preflight響應報文通常長成這樣:
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內無需為該URL發送preflight請求。
至于為何需要preflight請參考reference中的文章。
并不是所有的請求都需要發送preflight請求,服務器面對簡單請求會直接返回Access-Control-Allow-Origin響應頭來說明它的跨域訪問是否通過,如果通過,則會在響應體中直接攜帶數據。請求和響應報文如下:
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]
服務器會檢查origin字段的URL是否允許跨域請求。可以看到該服務器允許來自一切IP的跨域訪問,因為它返回的響應頭為Access-Control-Allow-Origin: *。
你會發現,這里的請求和一般的HTTP請求并沒有太大的差別。
那么,什么是簡單請求呢?
滿足以上要求的則為簡單請求。而通常前后端分離的服務之間會通過json形式的數據進行溝通,即content-type為application/json。而這種形式不符合簡單請求的定義,因此需要使用option請求進行預檢。
復雜請求的預檢請求報文如下:
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
請求頭中有兩個字段比較特殊。Access-Control-Request-Method說明真正的跨域請求的方法,這里是POST方法,而Access-Control-Request-Headers則說明請求頭中包含哪些非簡單字段。服務器端會根據自身的配置查看是否支持包含這些非簡單字段的請求,如果不包含,則該跨域請求會被拒絕。
預檢響應報文如下:
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說明了服務器支持的跨域請求的這些字段。
之后服務器會發送真實的請求,服務器會對之響應,其響應頭中會包含Access-Control-Allow-Origin字段。
身份認證 Spring-Boot 配置現在我們再來看一下之前的配置:
@Configuration public class WebConfig extends WebMvcConfigurationSupport { ...... public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/***")//對/api/**進行跨域配置 .allowedHeaders("**")//允許所有的非簡單請求頭 .allowedMethods("GET", "OPTIONS", "POST") //允許三種方法 .allowedOrigins("*");//允許來自所有域的請求 } }
當然這種全部符合的通配符并不是一個很好的選擇,我們應當限制跨域請求的形式,從而拒絕不符合要求的請求。
第二種配置是采用Filter的形式進行配置。位于最前面的Filter會在請求進入任何其它位置之前對其進行處理。
@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; } }
這里跟上面的配置意思基本相同,區別在于這里引入了setAllowCredential配置。它代表服務器支持跨域時攜帶認證信息。需要注意的是,如果開啟這個配置,則allowedOrigins不可以為*。
Referencespringboot設置cors跨域請求的兩種方式
spring官網-設置允許跨域請求
MDN Http 控制訪問
MDN Same Origin Policy
What"s the motivation of preflight
若想了解更多技術資訊、面試教程以及互聯網公司的內推信息,歡迎關注我的公眾號!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69504.html
摘要:基礎深度學習概念備忘錄后端掘金基礎深度學習概念備忘錄翻譯自。否則,試想在你捧著某出版社剛剛翻譯出來的高效編程苦規范及相關文檔前端掘金官方規范歲程序員的獨家面試經歷閱讀掘金創業失敗后,在找工作。 基礎深度學習概念備忘錄 - 后端 - 掘金基礎深度學習概念備忘錄翻譯自DeepLearning Cheat Sheet。筆者還是菜鳥一枚,若有謬誤請多多賜教,另外如果希望了解更多機器學習&深度學...
摘要:忍者級別的函數操作對于什么是匿名函數,這里就不做過多介紹了。我們需要知道的是,對于而言,匿名函數是一個很重要且具有邏輯性的特性。通常,匿名函數的使用情況是創建一個供以后使用的函數。 JS 中的遞歸 遞歸, 遞歸基礎, 斐波那契數列, 使用遞歸方式深拷貝, 自定義事件添加 這一次,徹底弄懂 JavaScript 執行機制 本文的目的就是要保證你徹底弄懂javascript的執行機制,如果...
閱讀 2306·2021-11-23 10:09
閱讀 2885·2021-10-12 10:11
閱讀 2594·2021-09-29 09:35
閱讀 1336·2019-08-30 15:53
閱讀 2260·2019-08-30 11:15
閱讀 2904·2019-08-29 13:01
閱讀 2290·2019-08-28 18:15
閱讀 3363·2019-08-26 12:13