SpringBootCORS配置詳解:允許跨域請求的最佳實踐

👉 這是一個或許對你有用的社群
🐱 一對一交流/面試小冊/簡歷最佳化/求職解惑,歡迎加入芋道快速開發平臺知識星球。下面是星球提供的部分資料:
👉這是一個或許對你有用的開源專案
國產 Star 破 10w+ 的開源專案,前端包括管理後臺 + 微信小程式,後端支援單體和微服務架構。
功能涵蓋 RBAC 許可權、SaaS 多租戶、資料許可權、商城、支付、工作流、大屏報表、微信公眾號、ERPCRMAI 大模型等等功能:
  • Boot 多模組架構:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 微服務架構:https://gitee.com/zhijiantianya/yudao-cloud
  • 影片教程:https://doc.iocoder.cn
【國內首批】支援 JDK 17/21 + SpringBoot 3.3、JDK 8/11 + Spring Boot 2.7 雙版本 

跨域請求的背景和重要性

在現代 Web 開發中,跨域請求是一個常見且重要的概念。隨著網際網路應用的日益複雜,尤其是在涉及多個前端和後端服務的情況下,跨域問題經常會對應用的功能和使用者體驗造成影響。

背景

開發專案時,遇到一個需求,當時專案配套的線下商鋪已由物業交付給我司運營,運營團隊需要招商引資,經過公司考慮決定開發一個公眾號交給運營團隊,以便客戶能夠線上選擇商鋪。
公眾號內嵌一個H5頁面,做到使用者點選平面圖中單元格(每一個商鋪號就是一個單元格)優先鎖定商鋪,由於商鋪分佈在多個區域,UI繪圖也需要時間,再加需求緊迫,專案必須在三天內上線。
前端團隊採用了 Canvas 技術,讓使用者能夠直觀地選擇商鋪單元格,並填寫提交個人資料。開發過程中,前後端進行了介面聯調,在測試環境中沒有明顯的問題。然而,當專案部署到微信公眾號後,出現了跨域請求問題,直接是空白頁面。
當時,前端因為配置代理的進度緩慢,跨域配置的解決方案轉到了後端。這一問題突顯了跨域請求在 Web 開發中的重要性,特別是在需要與多個服務進行互動時。

跨域請求的重要性

  • 安全性: 瀏覽器的同源政策旨在保護使用者,防止惡意網站竊取資訊。跨域請求需要經過嚴格的檢查和配置,以確保資料傳輸的安全性。
  • 使用者體驗: 跨域請求的限制可能會導致使用者在操作過程中遇到障礙,影響應用的流暢性和可用性。在我們的專案中,如果不及時解決跨域問題,將會直接影響客戶體驗和業務進展。
  • 業務需求: 在某些情況下,業務需求可能需要不同來源的資源互動。例如,在我們開發的微信公眾號中,需要與後端服務進行資料互動,以完成使用者的選擇和定金繳納等操作。
  • 快速迭代: 隨著專案的推進,及時處理跨域問題是確保專案快速上線的重要環節。在短時間內解決跨域配置,能夠為後續的功能擴充套件和業務發展打下良好的基礎。
基於 Spring Boot + MyBatis Plus + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 影片教程:https://doc.iocoder.cn/video/

什麼是跨域

跨域是指在 Web 應用中,由於瀏覽器的同源政策(Same-Origin Policy),不同源的網頁之間進行互動時所遇到的限制。源的定義包括三個部分:協議(如 http 或 https)、域名(如 example.com)和埠(如 80 或 443)。只有當這三者都相同的時候,兩個 URL 被認為是同源的。

為什麼有同源政策?

通俗來說,瀏覽器廠商開發出來的瀏覽器都是有做安全限制的,當你開啟某個網站時,瀏覽器就已經將請求標頭中的origin屬性改成了當前網站的域名。例如我訪問bilibili,會是這樣的一個origin,你在當前頁面中做以下幾種操作,均會出現跨域:

跨域的情形

1,http://www.bilibili.com(假設存在)
2,https://www.bilibili.com:8086(假設存在)
3,http://admin.www.bilibili.com(假設存在)

跨域原因解釋

情況1跨域的原因是scheme(標識特定協議或資源型別的字串)變了
情況2 跨域的原因是port(埠號)變了
情況3跨域的原因是host(域名,admin.www.bilibili.com是域名)變了
基於 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/yudao-cloud
  • 影片教程:https://doc.iocoder.cn/video/

如何證明上述情況就是跨域?

跨不跨域框架說了算,來看看Springboot框架是如何認定為跨域的,先附上截圖,然後給原始碼解釋
處理請求相關的引數,並透過比較來判斷是否跨域的原始碼
package

 org.springframework.web.cors;

publicabstractclassCorsUtils

{

publicCorsUtils()

{

    }

//方法名就直接體現了方法的作用,判斷是否是跨域請求
publicstaticbooleanisCorsRequest(HttpServletRequest request)

{

        String origin = request.getHeader(

"Origin"

);

if

 (origin == 

null

) {

returnfalse

;

        } 

else

 {

            UriComponents originUrl = UriComponentsBuilder.fromOriginHeader(origin).build();

            String scheme = request.getScheme();

            String host = request.getServerName();

int

 port = request.getServerPort();

//上面的程式碼是從請求體中獲取協議,域名,埠的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());

        }

    }

}

原始碼中不難看出來,在經過一番處理之後,會透過客戶端傳遞的Origin中的資訊和介面服務資源做協議,埠,域名的比對,只要有一處不一樣那就是跨域,框架會告知瀏覽器跨域,具體的比對過程並不難,我已經貼出來了包名和類名,鼓勵朋友們自己動手。

為什麼是這樣,而不是那樣

既然伺服器有處理請求,為什麼你在瀏覽器上看不到響應回來的HTTP狀態碼,伺服器應該要給客戶端返回個狀態碼,取而代之的卻是顯示:此請求沒有發起程式請求或者類似的其他提示,這都要歸功於預檢請求,也是瀏覽器廠商預設遵循的一個標準規範,屬於 CORS(跨源資源共享)機制的一部分。
跨域提示截圖
或者

預檢請求

預檢請求(Preflight Request)是 CORS(跨源資源共享)機制中的一個重要概念,用於在傳送複雜的跨域請求之前,先向伺服器傳送一個 HTTP OPTIONS 請求,以確認伺服器是否允許實際的請求。預檢請求的目的是為了增強安全性,確保客戶端在傳送敏感資料時得到伺服器的許可。

何時觸發預檢請求

預檢請求通常在以下情況下觸發:

複雜請求:

  • 當使用的 HTTP 方法不是簡單請求中的 GETPOST(如 PUTDELETE)。
  • 當請求中包含自定義頭部(例如,X-Custom-Header)。
  • 當 Content-Type 的值不是簡單請求允許的型別(如 application/x-www-form-urlencodedmultipart/form-datatext/plain)。

伺服器端的 CORS 配置:

  • 只有在伺服器配置了 CORS,並明確允許來自特定源的請求時,預檢請求才會返回成功。

預檢請求關伺服器什麼事情

完全不瞎說,有沒有預檢請求,依舊是springboot框架說了算,先附上原圖,在附上部分原始碼
當我從知乎頁面上請求我本機的服務介面時
伺服器處理預檢請求
首先伺服器確實收到了該次請求,截圖如下:
處理預檢請求的截圖:
OPTIONS請求就是預檢請求的請求方式,這裡解釋不了為什麼,只能回答這就是規範
處理預檢請求的原始碼:
publicstaticbooleanisPreFlightRequest(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

;

    }

伺服器如何處理跨域呢,允許還是不允許?

允許還是不允許,完全看程式設計師如何設定跨域規則,跨域策略,不做深入講解,但是教你如何避開雷區,先看看核心邏輯的截圖
伺服器會判斷當前是否是預檢請求,如果是,則會呼叫一個處理內部請求的方法,如圖
關鍵點:allowOrigin為什麼為null,checkOrigin方法到底做了什麼比較

知識點回顧

問題到這裡很清晰了,當程式執行到ObjectUtils.isEmpty(this.allowedOrigins)或者this.allowedOrigins.contains("*"),if語句的條件不成立了,因為this.allowedOrigins並不包含客戶端的域名,也就是例子中的https://www.bilbili.com或者https://www.zhihu.com,我們要處理的正是allowedOrigins
private

 List<String> allowedOrigins;

他是以陣列的形式被持有的,有很多個API可以給這個陣列初始化值,在我的程式碼中,只展示一種,因為我們要學的不是API,而是發現問題,拆分問題,解決問題的心法,API什麼的不重要。
以上介紹了什麼是跨域,跨域的情形,以及預檢請求作為web瀏覽器的規範,以及伺服器如何處理預檢請求,瀏覽器對於未透過的預檢請求會以什麼形式展示給使用者,接下來告訴大家如何解決這種小小的問題~
springboot解決跨域的方式非常之多,但是從最底層解決,往往能學到更多指定問題之外的知識

SpringBoot允許跨域的後端程式碼

@Configuration
publicclassCorsConfig

{

@Bean
public CorsFilter corsFilter()

{

        UrlBasedCorsConfigurationSource source = 

new

 UrlBasedCorsConfigurationSource();

        CorsConfiguration config = 

new

 CorsConfiguration();

//config.setAllowCredentials(true); // 允許傳送憑據,雷區

        config.addAllowedOrigin(

"*"

); 

//允許任意域名跨域訪問介面

        config.addAllowedHeader(

"*"

); 

// 允許所有頭部資訊

        config.addAllowedMethod(

"*"

); 

// 允許所有請求方法

        source.registerCorsConfiguration(

"/**"

, config); 

// 應用於所有路徑
returnnew

 CorsFilter(source);

    }

}

這段配置足已解決前端跨域問題,之前說的雷區就是允許傳送憑據的程式碼和config.addAllowedOrigin("*");不可以一起使用,否則會報錯。到這裡,一切OK,前端跨域的問題已經解決~
給大家一段便捷的JS程式碼用來測試跨域問題,JS程式碼不做解釋,相信看懂不成問題

模擬跨域的JS程式碼

var

 xhr = 

new

 XMLHttpRequest();

xhr.open(

'post'

'http://localhost:8081/admin/captcha/v1/generateCaptcha'

);

xhr.setRequestHeader(

'Content-Type'

'application/json'

); 

// 設定請求頭

xhr.setRequestHeader(

'authracation'

'abcdefghijklmnopqrstuvwxyz'

); 

// 設定請求頭

xhr.onload = function(e) {

var

 xhr = e.target;

    console.log(xhr.responseText);

};
xhr.send(

'{}'

);

這段js程式碼,按F12,在瀏覽器的控制檯中直接執行,支援IE和Google瀏覽器,親測有效,需要根據實際的請求進行微調,不要在你自己的WEB專案或者API文件頁面開啟,否則無法達到測試跨域的效果,具體原因,我相信你理解了上面的知識點之後應該能明白。解決問題的程式碼很少,但是知識點並不少,留心處處皆學問哈

歡迎加入我的知識星球,全面提升技術能力。
👉 加入方式,長按”或“掃描”下方二維碼噢
星球的內容包括:專案實戰、面試招聘、原始碼解析、學習路線。
文章有幫助的話,在看,轉發吧。
謝謝支援喲 (*^__^*)

相關文章