成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

跨域問題的一次深入研究

X_AirDu / 1601人閱讀

摘要:前言最近在業(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中的文章。

CORS報(bào)文

并不是所有的請(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不可以為*。

Reference

springboot設(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

相關(guān)文章

  • 文章大雜燴 - 收藏集 - 掘金

    摘要:基礎(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é)...

    wuyumin 評(píng)論0 收藏0
  • 前端相關(guān)大雜燴

    摘要:希望幫助更多的前端愛好者學(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 原文作...

    fuyi501 評(píng)論0 收藏0
  • JavasScript重難點(diǎn)知識(shí)

    摘要:忍者級(jí)別的函數(shù)操作對(duì)于什么是匿名函數(shù),這里就不做過(guò)多介紹了。我們需要知道的是,對(duì)于而言,匿名函數(shù)是一個(gè)很重要且具有邏輯性的特性。通常,匿名函數(shù)的使用情況是創(chuàng)建一個(gè)供以后使用的函數(shù)。 JS 中的遞歸 遞歸, 遞歸基礎(chǔ), 斐波那契數(shù)列, 使用遞歸方式深拷貝, 自定義事件添加 這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機(jī)制,如果...

    forsigner 評(píng)論0 收藏0
  • 徹底弄懂跨域問題

    摘要:用于告知瀏覽器可以將預(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...

    rose 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<