徹底搞懂跨域問題SpringBoot助你暢通無阻
環境:SpringBoot2.7.16
1. 簡介
跨源資源共享(CORS,或通俗地譯為跨域資源共享)是一種基于 HTTP 頭的機制,該機制通過允許服務器標示除了它自己以外的其他源(域、協議或端口),使得瀏覽器允許這些源訪問加載自己的資源??缭促Y源共享還通過一種機制來檢查服務器是否會允許要發送的真實請求,該機制通過瀏覽器發起一個到服務器托管的跨源資源的“預檢”請求。在預檢中,瀏覽器發送的頭中標示有 HTTP 方法和真實請求中會用到的頭。
跨源 HTTP 請求例子:運行在https://www.a.com 的 JavaScript 代碼使用 XMLHttpRequest 來發起一個到 https://www.b.com/data.json 的請求。
出于安全性,瀏覽器限制腳本內發起的跨源 HTTP 請求。例如,XMLHttpRequest 和 Fetch API 遵循同源策略。這意味著使用這些 API 的 Web 應用程序只能從加載應用程序的同一個域請求 HTTP 資源,除非響應報文包含了正確 CORS 響應頭。
圖片
什么情況下需要 CORS?
這份跨源共享標準允許在下列場景中使用跨站點 HTTP 請求:
- XMLHttpRequest 或 Fetch API 發起的跨源 HTTP 請求。
- Web 字體(CSS 中通過 @font-face 使用跨源字體資源),因此,網站就可以發布 TrueType 字體資源,并只允許已授權網站進行跨站調用。
- WebGL 貼圖。
- 使用 drawImage() 將圖片或視頻畫面繪制到 canvas。
- 來自圖像的 CSS 圖形 (en-US)。
跨源資源共享標準新增了一組 HTTP 標頭字段,允許服務器聲明哪些源站通過瀏覽器有權限訪問哪些資源。另外,規范要求,對那些可能對服務器數據產生副作用的 HTTP 請求方法(特別是 GET 以外的 HTTP 請求,或者搭配某些 MIME 類型的 POST 請求),瀏覽器必須首先使用 OPTIONS 方法發起一個預檢請求(preflight request),從而獲知服務端是否允許該跨源請求。服務器確認允許之后,才發起實際的 HTTP 請求。在預檢請求的返回中,服務器端也可以通知客戶端,是否需要攜帶身份憑證(例如 Cookie 和 HTTP 認證相關數據)。
CORS 請求失敗會產生錯誤,但是為了安全,在 JavaScript 代碼層面無法獲知到底具體是哪里出了問題。你只能查看瀏覽器的控制臺以得知具體是哪里出現了錯誤。
什么是預檢請求?
一個 CORS 預檢請求是用于檢查服務器是否支持 CORS 即跨域資源共享。它一般是用了以下幾個 HTTP 請求首部的 OPTIONS 請求:Access-Control-Request-Method 和 Access-Control-Request-Headers,以及一個 Origin 首部。當有必要的時候,瀏覽器會自動發出一個預檢請求;所以在正常情況下,前端開發者不需要自己去發這樣的請求。
舉個例子,一個客戶端可能會在實際發送一個 DELETE 請求之前,先向服務器發起一個預檢請求,用于詢問是否可以向服務器發起一個 DELETE 請求:
OPTIONS /resource/foo
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: origin, x-requested-with
Origin: https://foo.bar.org
如果服務器允許,那么服務器就會響應這個預檢請求。并且其響應頭 Access-Control-Allow-Methods 會將 DELETE 包含在其中:
HTTP/1.1 200 OK
Content-Length: 0
Connection: keep-alive
Access-Control-Allow-Origin: https://foo.bar.org
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Access-Control-Max-Age: 86400
針對CORS的HTTP Headers:
- Access-Control-Allow-Origin指示響應的資源是否可以被給定的來源共享。
- Access-Control-Allow-Credentials指示當請求的憑證標記為 true 時,是否可以公開對該請求響應。
- Access-Control-Allow-Headers用在對預檢請求的響應中,指示實際的請求中可以使用哪些 HTTP 標頭。
- Access-Control-Allow-Methods指定對預檢請求的響應中,哪些 HTTP 方法允許訪問請求的資源。
- Access-Control-Expose-Headers通過列出標頭的名稱,指示哪些標頭可以作為響應的一部分公開。
- Access-Control-Max-Age指示預檢請求的結果能被緩存多久。
- Access-Control-Request-Headers用于發起一個預檢請求,告知服務器正式請求會使用哪些 HTTP 標頭。
- Access-Control-Request-Method用于發起一個預檢請求,告知服務器正式請求會使用哪一種 HTTP 請求方法。
- Origin指示獲取資源的請求是從什么源發起的。
- Timing-Allow-Origin指定特定的源,以允許其訪問 Resource Timing API 功能提供的屬性值,否則由于跨源限制,這些值將被報告為零。
大致了解了CORS后,接下來介紹在SpringBoot中如何解決跨域問題
2. 實戰案例
Spring MVC HandlerMapping實現提供了對CORS的內置支持。在成功地將請求映射到處理程序之后,HandlerMapping實現檢查給定請求和處理程序的CORS配置,并采取進一步的操作。Preflight requests(預檢請求)被直接處理,而簡單和實際的CORS請求被攔截、驗證,并設置了所需的CORS響應頭。
2.1 @CrossOrigin
@CrossOrigin注釋允許對帶注釋的控制器方法進行跨域請求,如下例所示:
@RestController
@RequestMapping("/accounts")
public class AccountController {
@CrossOrigin
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
默認情況下,@CrossOrigin允許:
- 所有請求來源origins。
- 所有請求headers。
- 控制器方法映射到的所有HTTP方法。
注意:默認情況下不啟用allowCredentials,因為它建立了一個公開敏感的用戶特定信息(如Cookie和CSRF令牌)的信任級別,并且只應在適當的地方使用。啟用時,必須將allowOrigins設置為一個或多個特定域(但不是特殊值“*”),或者可以使用allowOringPatterns屬性來匹配一組動態原點。
maxAge 默認30分鐘。
@CrossOrigin在類級別也受支持,并由所有方法繼承,如下例所示:
@CrossOrigin(origins = "https://www.pack.com", maxAge = 3600)
@RestController
@RequestMapping("/accounts")
public class AccountController {
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
}
你可以在類級別和方法級別使用@CrossOrigin,如下例所示:
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/accounts")
public class AccountController {
@CrossOrigin("http://www.pack.com")
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
}
2.2 全局配置
除了細粒度的控制器方法級配置外,你還可以全局CORS配置??梢栽谌魏蜨andlerMapping上單獨設置基于URL的CorsConfiguration映射。
默認情況下,全局配置啟用以下功能:
- 所有請求來源origins.
- 所有請求headers.
- GET, HEAD, and POST methods.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://www.pack.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(true).maxAge(3600) ;
}
}
2.3 CORS過濾器
可以通過內置的CorsFilter應用CORS支持。
注意:如果你嘗試將CorsFilter與Spring Security一起使用,請記住Spring Security內置了對CORS的支持。
要配置篩選器,請將CorsConfigurationSource傳遞給其構造函數,如下例所示:
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration() ;
config.setAllowCredentials(true) ;
config.addAllowedOrigin("http://www.pack.com") ;
config.addAllowedHeader("*") ;
config.addAllowedMethod("*") ;
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource() ;
source.registerCorsConfiguration("/**", config) ;
CorsFilter filter = new CorsFilter(source) ;
return filter ;
}
當然你也可以使用自定義的Filter來解決CORS問題。
@WebFilter("/*")
public class WebCORSFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res ;
HttpServletRequest request = (HttpServletRequest) req ;
String origin = request.getHeader("Origin") ;
request.setCharacterEncoding("UTF-8") ;
response.setHeader("Access-Control-Allow-Origin", origin) ;
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT") ;
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Credentials", "true") ;
response.setHeader("Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Account, access-token") ;
chain.doFilter(req, res) ;
}
}
以上是本篇文章的全部內容,希望對你有幫助。