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