CORS

Spring WebFlux 允許您處理 CORS(跨域資源共享)。本節介紹如何實現。

簡介

出於安全原因,瀏覽器禁止 AJAX 呼叫當前來源之外的資源。例如,您可能在一個標籤頁中開啟您的銀行賬戶,在另一個標籤頁中開啟 evil.com。來自 evil.com 的指令碼不應能夠使用您的憑據向您的銀行 API 發出 AJAX 請求——例如,從您的賬戶中提款!

跨域資源共享(CORS)是 W3C 規範,由 大多數瀏覽器 實現,它允許您指定授權的跨域請求型別,而不是使用基於 IFRAME 或 JSONP 的安全性較低且功能較弱的變通方法。

處理

CORS 規範區分預檢請求、簡單請求和實際請求。要了解 CORS 的工作原理,您可以閱讀 本文(以及許多其他文章),或參閱規範瞭解更多詳情。

Spring WebFlux HandlerMapping 實現提供了對 CORS 的內建支援。在成功將請求對映到處理器後,HandlerMapping 會檢查給定請求和處理器的 CORS 配置並採取進一步操作。預檢請求直接處理,而簡單和實際的 CORS 請求則被攔截、驗證並設定所需的 CORS 響應頭。

為了啟用跨域請求(即存在 Origin 頭且與請求的主機不同),您需要有一些明確宣告的 CORS 配置。如果找不到匹配的 CORS 配置,預檢請求將被拒絕。簡單和實際 CORS 請求的響應中不新增 CORS 頭,因此瀏覽器會拒絕它們。

每個 HandlerMapping 都可以單獨透過基於 URL 模式的 CorsConfiguration 對映進行配置。在大多數情況下,應用程式使用 WebFlux Java 配置來宣告此類對映,這會生成一個傳遞給所有 HandlerMapping 實現的全域性對映。

您可以將 HandlerMapping 級別的全域性 CORS 配置與更細粒度的處理器級別 CORS 配置相結合。例如,帶註解的控制器可以使用類級別或方法級別的 @CrossOrigin 註解(其他處理器可以實現 CorsConfigurationSource)。

組合全域性和區域性配置的規則通常是附加的——例如,所有全域性來源和所有區域性來源。對於只能接受單個值的屬性,例如 allowCredentialsmaxAge,區域性配置會覆蓋全域性值。有關更多詳細資訊,請參閱 CorsConfiguration#combine(CorsConfiguration)

要從原始碼瞭解更多資訊或進行高階自定義,請參閱

  • CorsConfiguration

  • CorsProcessorDefaultCorsProcessor

  • AbstractHandlerMapping

帶憑據的請求

將 CORS 與帶憑據的請求一起使用需要啟用 allowedCredentials。請注意,此選項與配置的域建立了高度信任,並透過暴露敏感的使用者特定資訊(如 cookie 和 CSRF 令牌)增加了 Web 應用程式的攻擊面。

啟用憑據還會影響配置的 "*" CORS 萬用字元的處理方式

  • allowOrigins 中不允許使用萬用字元,但可以使用 allowOriginPatterns 屬性來匹配動態來源集。

  • 當在 allowedHeadersallowedMethods 上設定時,Access-Control-Allow-HeadersAccess-Control-Allow-Methods 響應頭透過複製 CORS 預檢請求中指定的關聯頭和方法來處理。

  • 當在 exposedHeaders 上設定時,Access-Control-Expose-Headers 響應頭會設定為配置的頭列表或萬用字元。雖然當 Access-Control-Allow-Credentials 設定為 true 時,CORS 規範不允許使用萬用字元,但大多數瀏覽器支援它,並且並非所有響應頭都在 CORS 處理期間可用,因此,無論 allowCredentials 屬性的值如何,指定萬用字元時,它就是使用的頭值。

雖然這種萬用字元配置可能很方便,但建議在可能的情況下配置有限的值集,以提供更高級別的安全性。

@CrossOrigin

@CrossOrigin 註解在帶註解的控制器方法上啟用跨域請求,如下例所示

  • Java

  • Kotlin

@RestController
@RequestMapping("/account")
public class AccountController {

	@CrossOrigin
	@GetMapping("/{id}")
	public Mono<Account> retrieve(@PathVariable Long id) {
		// ...
	}

	@DeleteMapping("/{id}")
	public Mono<Void> remove(@PathVariable Long id) {
		// ...
	}
}
@RestController
@RequestMapping("/account")
class AccountController {

	@CrossOrigin
	@GetMapping("/{id}")
	suspend fun retrieve(@PathVariable id: Long): Account {
		// ...
	}

	@DeleteMapping("/{id}")
	suspend fun remove(@PathVariable id: Long) {
		// ...
	}
}

預設情況下,@CrossOrigin 允許

  • 所有來源。

  • 所有頭。

  • 控制器方法對映到的所有 HTTP 方法。

預設情況下不啟用 allowCredentials,因為它建立了暴露敏感使用者特定資訊(如 cookie 和 CSRF 令牌)的信任級別,應僅在適當的情況下使用。啟用時,allowOrigins 必須設定為一個或多個特定域(但不能是特殊值 "*"),或者可以使用 allowOriginPatterns 屬性來匹配動態來源集。

maxAge 設定為 30 分鐘。

@CrossOrigin 也支援類級別,並由所有方法繼承。以下示例指定了一個特定域並將 maxAge 設定為一小時

  • Java

  • Kotlin

@CrossOrigin(origins = "https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {

	@GetMapping("/{id}")
	public Mono<Account> retrieve(@PathVariable Long id) {
		// ...
	}

	@DeleteMapping("/{id}")
	public Mono<Void> remove(@PathVariable Long id) {
		// ...
	}
}
@CrossOrigin("https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
class AccountController {

	@GetMapping("/{id}")
	suspend fun retrieve(@PathVariable id: Long): Account {
		// ...
	}

	@DeleteMapping("/{id}")
	suspend fun remove(@PathVariable id: Long) {
		// ...
	}
}

您可以在類級別和方法級別使用 @CrossOrigin,如下例所示

  • Java

  • Kotlin

@CrossOrigin(maxAge = 3600) (1)
@RestController
@RequestMapping("/account")
public class AccountController {

	@CrossOrigin("https://domain2.com") (2)
	@GetMapping("/{id}")
	public Mono<Account> retrieve(@PathVariable Long id) {
		// ...
	}

	@DeleteMapping("/{id}")
	public Mono<Void> remove(@PathVariable Long id) {
		// ...
	}
}
1 在類級別使用 @CrossOrigin
2 在方法級別使用 @CrossOrigin
@CrossOrigin(maxAge = 3600) (1)
@RestController
@RequestMapping("/account")
class AccountController {

	@CrossOrigin("https://domain2.com") (2)
	@GetMapping("/{id}")
	suspend fun retrieve(@PathVariable id: Long): Account {
		// ...
	}

	@DeleteMapping("/{id}")
	suspend fun remove(@PathVariable id: Long) {
		// ...
	}
}
1 在類級別使用 @CrossOrigin
2 在方法級別使用 @CrossOrigin

全域性配置

除了細粒度的控制器方法級別配置之外,您可能還想定義一些全域性 CORS 配置。您可以在任何 HandlerMapping 上單獨設定基於 URL 的 CorsConfiguration 對映。然而,大多數應用程式使用 WebFlux Java 配置來完成此操作。

預設情況下,全域性配置啟用以下功能

  • 所有來源。

  • 所有頭。

  • GETHEADPOST 方法。

預設情況下不啟用 allowedCredentials,因為它建立了暴露敏感使用者特定資訊(如 cookie 和 CSRF 令牌)的信任級別,應僅在適當的情況下使用。啟用時,allowOrigins 必須設定為一個或多個特定域(但不能是特殊值 "*"),或者可以使用 allowOriginPatterns 屬性來匹配動態來源集。

maxAge 設定為 30 分鐘。

要在 WebFlux Java 配置中啟用 CORS,您可以使用 CorsRegistry 回撥,如下例所示

  • Java

  • Kotlin

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

	@Override
	public void addCorsMappings(CorsRegistry registry) {

		registry.addMapping("/api/**")
			.allowedOrigins("https://domain2.com")
			.allowedMethods("PUT", "DELETE")
			.allowedHeaders("header1", "header2", "header3")
			.exposedHeaders("header1", "header2")
			.allowCredentials(true).maxAge(3600);

		// Add more mappings...
	}
}
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

	override fun addCorsMappings(registry: CorsRegistry) {

		registry.addMapping("/api/**")
				.allowedOrigins("https://domain2.com")
				.allowedMethods("PUT", "DELETE")
				.allowedHeaders("header1", "header2", "header3")
				.exposedHeaders("header1", "header2")
				.allowCredentials(true).maxAge(3600)

		// Add more mappings...
	}
}

CORS WebFilter

您可以透過內建的 CorsWebFilter 應用 CORS 支援,它非常適合 函式式端點

如果您嘗試將 CorsFilter 與 Spring Security 一起使用,請記住 Spring Security 具有 內建的 CORS 支援

要配置過濾器,您可以宣告一個 CorsWebFilter bean 並將 CorsConfigurationSource 傳遞給其建構函式,如下例所示

  • Java

  • Kotlin

@Bean
CorsWebFilter corsFilter() {

	CorsConfiguration config = new CorsConfiguration();

	// Possibly...
	// config.applyPermitDefaultValues()

	config.setAllowCredentials(true);
	config.addAllowedOrigin("https://domain1.com");
	config.addAllowedHeader("*");
	config.addAllowedMethod("*");

	UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
	source.registerCorsConfiguration("/**", config);

	return new CorsWebFilter(source);
}
@Bean
fun corsFilter(): CorsWebFilter {

	val config = CorsConfiguration()

	// Possibly...
	// config.applyPermitDefaultValues()

	config.allowCredentials = true
	config.addAllowedOrigin("https://domain1.com")
	config.addAllowedHeader("*")
	config.addAllowedMethod("*")

	val source = UrlBasedCorsConfigurationSource().apply {
		registerCorsConfiguration("/**", config)
	}
	return CorsWebFilter(source)
}
© . This site is unofficial and not affiliated with VMware.