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