SpringBoot 項目處理跨域的四種技巧
上周幫一家公司優(yōu)化代碼時,順手把跨域的問題解決了, 這篇文章,我們聊聊 SpringBoot 項目處理跨域的四種技巧 。
圖片
一、什么是跨域
我們先看下一個典型的網(wǎng)站的地址:
圖片
同源是指:協(xié)議、域名、端口號完全相同。
下表給出了與 URL http://www.training.com/dir/page.html 的源進(jìn)行對比的示例 :
圖片
當(dāng)用戶通過瀏覽器訪問應(yīng)用(http://admin.training.com)時,調(diào)用接口的域名非同源域名(http://api.training.com),這是顯而易見的跨域場景。
二、理解 CORS
CORS 是一個 W3C 標(biāo)準(zhǔn),全稱是"跨域資源共享"(Cross-origin resource sharing), 它需要瀏覽器和服務(wù)器同時支持他,允許瀏覽器向跨源服務(wù)器發(fā)送XMLHttpRequest請求,從而克服 AJAX 只能同源使用的限制。
跨域資源共享標(biāo)準(zhǔn)新增了一組 HTTP 首部字段,允許服務(wù)器聲明哪些源站通過瀏覽器有權(quán)限訪問哪些資源。
規(guī)范要求,對那些可能對服務(wù)器數(shù)據(jù)產(chǎn)生副作用的 HTTP 請求方法(特別是 GET 以外的 HTTP 請求,或者搭配某些 MIME 類型的 POST 請求),瀏覽器必須首先使用 OPTIONS 方法發(fā)起一個預(yù)檢請求(preflight request),從而獲知服務(wù)端是否允許該跨域請求。
服務(wù)器確認(rèn)允許之后,才發(fā)起實際的 HTTP 請求。在預(yù)檢請求的返回中,服務(wù)器端也可以通知客戶端,是否需要攜帶身份憑證(包括 Cookies 和 HTTP 認(rèn)證相關(guān)數(shù)據(jù))。
圖片
1.簡單請求
簡單請求模式,瀏覽器直接發(fā)送跨域請求,并在請求頭中攜帶 Origin 的頭,表明這是一個跨域的請求。 服務(wù)器端接到請求后,會根據(jù)自己的跨域規(guī)則,通過 Access-Control-Allow-Origin 和 Access-Control-Allow-Methods 響應(yīng)頭,來返回驗證結(jié)果。
圖片
2.預(yù)檢請求
瀏覽器在發(fā)現(xiàn)頁面發(fā)出的請求非簡單請求,并不會立即執(zhí)行對應(yīng)的請求代碼,而是會觸發(fā)預(yù)先請求模式。預(yù)先請求模式會先發(fā)送preflight request(預(yù)先驗證請求),preflight request是一個 OPTION 請求,用于詢問要被跨域訪問的服務(wù)器,是否允許當(dāng)前域名下的頁面發(fā)送跨域的請求。在得到服務(wù)器的跨域授權(quán)后才能發(fā)送真正的 HTTP 請求。
圖片
三、Nginx 配置
圖片
我們不用配置 SpringBoot 項目,在反向代理層 Nginx 直接配置 CORS ,典型配置如下圖:
圖片
四、配置類實現(xiàn) addCorsMapping 接口
SpringBoot 中新增一個配置類 CorsConfig.java,繼承 WebMvcConfigurerAdapter 或者實現(xiàn)WebMvcConfigurer 接口,項目啟動后,會自動讀取配置。
圖片
五、CorsFilter 過濾器模式
下圖是 SpringMvc 模式里,過濾器,攔截器,控制器的執(zhí)行順序。
圖片
CorsFilter 過濾器模式的優(yōu)點是:優(yōu)先級高,可以規(guī)避代碼中業(yè)務(wù)攔截器異常導(dǎo)致 adCorsMappings 方法失效的問題。
我們需要定義一個 corsFilter 方法,@Bean 注解表示此方法返回一個Spring Bean,該 Bean 將由Spring 容器管理。
corsFilter() 方法定義了一個 FilterRegistrationBean,這個 bean 是用來注冊 CorsFilter 的,后者用于處理 CORS 請求。
圖片
六、網(wǎng)關(guān)模式
在微服務(wù)架構(gòu)體系中,網(wǎng)關(guān)是非常核心的組件。 API 網(wǎng)關(guān)可以做鑒權(quán),限流,灰度等,同時可以配置 CORS 。內(nèi)部服務(wù)端不用特別關(guān)注跨域這個問題。
圖片
因此假如是 SpringCloud 體系,我們只需要配置 SpringCloud gateway 的跨域即可。