Spring Boot CORS 配置方法允許跨域請(qǐng)求的最佳實(shí)踐方案
跨域請(qǐng)求的背景和重要性
在現(xiàn)代 Web 開發(fā)中,跨域請(qǐng)求是一個(gè)常見且重要的概念。隨著互聯(lián)網(wǎng)應(yīng)用的日益復(fù)雜,尤其是在涉及多個(gè)前端和后端服務(wù)的情況下,跨域問題經(jīng)常會(huì)對(duì)應(yīng)用的功能和用戶體驗(yàn)造成影響。
背景
開發(fā)項(xiàng)目時(shí),遇到一個(gè)需求,當(dāng)時(shí)項(xiàng)目配套的線下商鋪已由物業(yè)交付給我司運(yùn)營(yíng),運(yùn)營(yíng)團(tuán)隊(duì)需要招商引資,經(jīng)過公司考慮決定開發(fā)一個(gè)公眾號(hào)交給運(yùn)營(yíng)團(tuán)隊(duì),以便客戶能夠在線選擇商鋪。公眾號(hào)內(nèi)嵌一個(gè)H5頁(yè)面,做到用戶點(diǎn)擊平面圖中單元格(每一個(gè)商鋪號(hào)就是一個(gè)單元格)優(yōu)先鎖定商鋪,由于商鋪分布在多個(gè)區(qū)域,UI繪圖也需要時(shí)間,再加需求緊迫,項(xiàng)目必須在三天內(nèi)上線。前端團(tuán)隊(duì)采用了 Canvas 技術(shù),讓用戶能夠直觀地選擇商鋪單元格,并填寫提交個(gè)人資料。開發(fā)過程中,前后端進(jìn)行了接口聯(lián)調(diào),在測(cè)試環(huán)境中沒有明顯的問題。然而,當(dāng)項(xiàng)目部署到微信公眾號(hào)后,出現(xiàn)了跨域請(qǐng)求問題,直接是空白頁(yè)面。
當(dāng)時(shí),前端因?yàn)榕渲么淼倪M(jìn)度緩慢,跨域配置的解決方案轉(zhuǎn)到了后端。這一問題突顯了跨域請(qǐng)求在 Web 開發(fā)中的重要性,特別是在需要與多個(gè)服務(wù)進(jìn)行交互時(shí)。
跨域請(qǐng)求的重要性
- 安全性:瀏覽器的同源政策旨在保護(hù)用戶,防止惡意網(wǎng)站竊取信息??缬蛘?qǐng)求需要經(jīng)過嚴(yán)格的檢查和配置,以確保數(shù)據(jù)傳輸?shù)陌踩浴?/li>
- 用戶體驗(yàn):跨域請(qǐng)求的限制可能會(huì)導(dǎo)致用戶在操作過程中遇到障礙,影響應(yīng)用的流暢性和可用性。在我們的項(xiàng)目中,如果不及時(shí)解決跨域問題,將會(huì)直接影響客戶體驗(yàn)和業(yè)務(wù)進(jìn)展。
- 業(yè)務(wù)需求:在某些情況下,業(yè)務(wù)需求可能需要不同來(lái)源的資源交互。例如,在我們開發(fā)的微信公眾號(hào)中,需要與后端服務(wù)進(jìn)行數(shù)據(jù)交互,以完成用戶的選擇和定金繳納等操作。
- 快速迭代:隨著項(xiàng)目的推進(jìn),及時(shí)處理跨域問題是確保項(xiàng)目快速上線的重要環(huán)節(jié)。在短時(shí)間內(nèi)解決跨域配置,能夠?yàn)楹罄m(xù)的功能擴(kuò)展和業(yè)務(wù)發(fā)展打下良好的基礎(chǔ)。
什么是跨域
跨域是指在 Web 應(yīng)用中,由于瀏覽器的同源政策(Same-Origin Policy),不同源的網(wǎng)頁(yè)之間進(jìn)行交互時(shí)所遇到的限制。源的定義包括三個(gè)部分:協(xié)議(如 http
或 https
)、域名(如 example.com
)和端口(如 80
或 443
)。只有當(dāng)這三者都相同的時(shí)候,兩個(gè) URL 被認(rèn)為是同源的。
為什么有同源政策?
通俗來(lái)說(shuō),瀏覽器廠商開發(fā)出來(lái)的瀏覽器都是有做安全限制的,當(dāng)你打開某個(gè)網(wǎng)站時(shí),瀏覽器就已經(jīng)將請(qǐng)求標(biāo)頭中的origin屬性改成了當(dāng)前網(wǎng)站的域名。例如我訪問bilibili,會(huì)是這樣的一個(gè)origin,你在當(dāng)前頁(yè)面中做以下幾種操作,均會(huì)出現(xiàn)跨域:
跨域的情形
1,http://www.bilibili.com(假設(shè)存在)
2,https://www.bilibili.com:8086(假設(shè)存在)
3,http://admin.www.bilibili.com(假設(shè)存在)
跨域原因解釋
情況1跨域的原因是scheme(標(biāo)識(shí)特定協(xié)議或資源類型的字符串)變了
情況2 跨域的原因是port(端口號(hào))變了
情況3跨域的原因是host(域名,admin.www.bilibili.com是域名)變了
如何證明上述情況就是跨域?
跨不跨域框架說(shuō)了算,來(lái)看看Springboot框架是如何認(rèn)定為跨域的,先附上截圖,然后給源碼解釋
處理請(qǐng)求相關(guān)的參數(shù),并通過比較來(lái)判斷是否跨域的源碼
package org.springframework.web.cors; public abstract class CorsUtils { public CorsUtils() { } //方法名就直接體現(xiàn)了方法的作用,判斷是否是跨域請(qǐng)求 public static boolean isCorsRequest(HttpServletRequest request) { String origin = request.getHeader("Origin"); if (origin == null) { return false; } else { UriComponents originUrl = UriComponentsBuilder.fromOriginHeader(origin).build(); String scheme = request.getScheme(); String host = request.getServerName(); int port = request.getServerPort(); //上面的代碼是從請(qǐng)求體中獲取協(xié)議,域名,端口的value值,拿到這些值就是為了和Origin作比較 //通過截圖也能看到Origin包含了scheme,host,port,他們分別是https,www.bilibili.com,443 return !ObjectUtils.nullSafeEquals(scheme, originUrl.getScheme()) || !ObjectUtils.nullSafeEquals(host, originUrl.getHost()) || getPort(scheme, port) != getPort(originUrl.getScheme(), originUrl.getPort()); } } }
源碼中不難看出來(lái),在經(jīng)過一番處理之后,會(huì)通過客戶端傳遞的Origin中的信息和接口服務(wù)資源做協(xié)議,端口,域名的比對(duì),只要有一處不一樣那就是跨域,框架會(huì)告知瀏覽器跨域,具體的比對(duì)過程并不難,我已經(jīng)貼出來(lái)了包名和類名,鼓勵(lì)朋友們自己動(dòng)手。
為什么是這樣,而不是那樣
既然服務(wù)器有處理請(qǐng)求,為什么你在瀏覽器上看不到響應(yīng)回來(lái)的HTTP狀態(tài)碼,服務(wù)器應(yīng)該要給客戶端返回個(gè)狀態(tài)碼,取而代之的卻是顯示:此請(qǐng)求沒有發(fā)起程序請(qǐng)求或者類似的其他提示,這都要?dú)w功于預(yù)檢請(qǐng)求,也是瀏覽器廠商默認(rèn)遵循的一個(gè)標(biāo)準(zhǔn)規(guī)范,屬于 CORS(跨源資源共享)機(jī)制的一部分。
跨域提示截圖
或者
預(yù)檢請(qǐng)求
預(yù)檢請(qǐng)求(Preflight Request)是 CORS(跨源資源共享)機(jī)制中的一個(gè)重要概念,用于在發(fā)送復(fù)雜的跨域請(qǐng)求之前,先向服務(wù)器發(fā)送一個(gè) HTTP OPTIONS 請(qǐng)求,以確認(rèn)服務(wù)器是否允許實(shí)際的請(qǐng)求。預(yù)檢請(qǐng)求的目的是為了增強(qiáng)安全性,確??蛻舳嗽诎l(fā)送敏感數(shù)據(jù)時(shí)得到服務(wù)器的許可。
何時(shí)觸發(fā)預(yù)檢請(qǐng)求
預(yù)檢請(qǐng)求通常在以下情況下觸發(fā):
1.復(fù)雜請(qǐng)求:
- 當(dāng)使用的 HTTP 方法不是簡(jiǎn)單請(qǐng)求中的 GET 或 POST(如 PUT、DELETE)。
- 當(dāng)請(qǐng)求中包含自定義頭部(例如,
X-Custom-Header
)。 - 當(dāng)
Content-Type
的值不是簡(jiǎn)單請(qǐng)求允許的類型(如application/x-www-form-urlencoded
、multipart/form-data
或text/plain
)。
2.服務(wù)器端的 CORS 配置:
- 只有在服務(wù)器配置了 CORS,并明確允許來(lái)自特定源的請(qǐng)求時(shí),預(yù)檢請(qǐng)求才會(huì)返回成功。
預(yù)檢請(qǐng)求關(guān)服務(wù)器什么事情
完全不瞎說(shuō),有沒有預(yù)檢請(qǐng)求,依舊是springboot框架說(shuō)了算,先附上原圖,在附上部分源碼
當(dāng)我從知乎頁(yè)面上請(qǐng)求我本機(jī)的服務(wù)接口時(shí)
服務(wù)器處理預(yù)檢請(qǐng)求
首先服務(wù)器確實(shí)收到了該次請(qǐng)求,截圖如下:
處理預(yù)檢請(qǐng)求的截圖:
OPTIONS請(qǐng)求就是預(yù)檢請(qǐng)求的請(qǐng)求方式,這里解釋不了為什么,只能回答這就是規(guī)范
處理預(yù)檢請(qǐng)求的源碼:
public static boolean isPreFlightRequest(HttpServletRequest request) { //先判斷是不是OPTIONS請(qǐng)求,若是,則表示是預(yù)檢請(qǐng)求 return HttpMethod.OPTIONS.matches(request.getMethod()) //預(yù)檢請(qǐng)求時(shí),http請(qǐng)求頭一定要給Origin && request.getHeader("Origin") != null //預(yù)檢請(qǐng)求時(shí),會(huì)給定名為Access-Control-Request-Method的請(qǐng)求頭 && request.getHeader("Access-Control-Request-Method") != null; }
服務(wù)器如何處理跨域呢,允許還是不允許?
允許還是不允許,完全看程序員如何設(shè)置跨域規(guī)則,跨域策略,不做深入講解,但是教你如何避開雷區(qū),先看看核心邏輯的截圖
服務(wù)器會(huì)判斷當(dāng)前是否是預(yù)檢請(qǐng)求,如果是,則會(huì)調(diào)用一個(gè)處理內(nèi)部請(qǐng)求的方法,如圖
關(guān)鍵點(diǎn):allowOrigin為什么為null,checkOrigin方法到底做了什么比較
知識(shí)點(diǎn)回顧
問題到這里很清晰了,當(dāng)程序執(zhí)行到ObjectUtils.isEmpty(this.allowedOrigins)或者this.allowedOrigins.contains("*"),if語(yǔ)句的條件不成立了,因?yàn)閠his.allowedOrigins并不包含客戶端的域名,也就是例子中的https://www.bilbili.com或者h(yuǎn)ttps://www.zhihu.com,我們要處理的正是allowedOrigins,
private List<String> allowedOrigins;
他是以數(shù)組的形式被持有的,有很多個(gè)API可以給這個(gè)數(shù)組初始化值,在我的代碼中,只展示一種,因?yàn)槲覀円獙W(xué)的不是API,而是發(fā)現(xiàn)問題,拆分問題,解決問題的心法,API什么的不重要。
以上介紹了什么是跨域,跨域的情形,以及預(yù)檢請(qǐng)求作為web瀏覽器的規(guī)范,以及服務(wù)器如何處理預(yù)檢請(qǐng)求,瀏覽器對(duì)于未通過的預(yù)檢請(qǐng)求會(huì)以什么形式展示給用戶,接下來(lái)告訴大家如何解決這種小小的問題~
springboot解決跨域的方式非常之多,但是從最底層解決,往往能學(xué)到更多指定問題之外的知識(shí)
SpringBoot允許跨域的后端代碼
@Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); //config.setAllowCredentials(true); // 允許發(fā)送憑據(jù),雷區(qū) config.addAllowedOrigin("*"); //允許任意域名跨域訪問接口 config.addAllowedHeader("*"); // 允許所有頭部信息 config.addAllowedMethod("*"); // 允許所有請(qǐng)求方法 source.registerCorsConfiguration("/**", config); // 應(yīng)用于所有路徑 return new CorsFilter(source); } }
這段配置足已解決前端跨域問題,之前說(shuō)的雷區(qū)就是允許發(fā)送憑據(jù)的代碼和config.addAllowedOrigin("*");不可以一起使用,否則會(huì)報(bào)錯(cuò)。到這里,一切OK,前端跨域的問題已經(jīng)解決~
給大家一段便捷的JS代碼用來(lái)測(cè)試跨域問題,JS代碼不做解釋,相信看懂不成問題
模擬跨域的JS代碼
var xhr = new XMLHttpRequest(); xhr.open('post', 'http://localhost:8081/admin/captcha/v1/generateCaptcha'); xhr.setRequestHeader('Content-Type', 'application/json'); // 設(shè)置請(qǐng)求頭 xhr.setRequestHeader('authracation', 'abcdefghijklmnopqrstuvwxyz'); // 設(shè)置請(qǐng)求頭 xhr.onload = function(e) { var xhr = e.target; console.log(xhr.responseText); }; xhr.send('{}');
這段js代碼,按F12,在瀏覽器的控制臺(tái)中直接執(zhí)行,支持IE和Google瀏覽器,親測(cè)有效,需要根據(jù)實(shí)際的請(qǐng)求進(jìn)行微調(diào),不要在你自己的WEB項(xiàng)目或者API文檔頁(yè)面打開,否則無(wú)法達(dá)到測(cè)試跨域的效果,具體原因,我相信你理解了上面的知識(shí)點(diǎn)之后應(yīng)該能明白。解決問題的代碼很少,但是知識(shí)點(diǎn)并不少,留心處處皆學(xué)問哈
到此這篇關(guān)于Spring Boot CORS 配置詳解:允許跨域請(qǐng)求的最佳實(shí)踐的文章就介紹到這了,更多相關(guān)Spring Boot CORS 跨域請(qǐng)求內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot或SpringAI對(duì)接DeepSeek大模型的詳細(xì)步驟
這篇文章主要介紹了DeepSeek智能助手的使用方法和步驟,包括引入庫(kù)、配置環(huán)境變量和配置,文章詳細(xì)描述了流式請(qǐng)求和非流式請(qǐng)求的實(shí)現(xiàn)方式,需要的朋友可以參考下2025-02-02java 同步器SynchronousQueue詳解及實(shí)例
這篇文章主要介紹了java 同步器SynchronousQueue詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-05-05Java詳解對(duì)象終止方法finalize()的用法
在前面的 jvm 中, 需要補(bǔ)充幾個(gè)部分的內(nèi)容, 接著來(lái)看 finalize() 機(jī)制, 它可以使接近死亡的對(duì)象復(fù)活, 下來(lái)我們來(lái)看是怎么一回事2022-05-05詳解Java并發(fā)編程中的優(yōu)先級(jí)隊(duì)列PriorityBlockingQueue
PriorityBlockingQueue是Java中實(shí)現(xiàn)了堆數(shù)據(jù)結(jié)構(gòu)的線程安全的有界阻塞隊(duì)列。本文將會(huì)深入解讀PriorityBlockingQueue的源碼實(shí)現(xiàn),感興趣的可以了解一下2023-05-05帶你了解Java數(shù)據(jù)結(jié)構(gòu)和算法之哈希表
這篇文章主要為大家介紹了Java數(shù)據(jù)結(jié)構(gòu)和算法之哈希表,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-01-01Springboot導(dǎo)入本地jar后 打包依賴無(wú)法加入的解決方案
這篇文章主要介紹了Springboot導(dǎo)入本地jar后 打包依賴無(wú)法加入的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11