CORS
Spring WebFlux 可讓您處理 CORS(跨來源資源共享)。本節說明如何執行此操作。
簡介
基於安全考量,瀏覽器禁止對目前來源以外的資源進行 AJAX 呼叫。例如,您可能在一個分頁中有您的銀行帳戶,而在另一個分頁中有 evil.com。來自 evil.com 的腳本不應能夠使用您的憑證對您的銀行 API 提出 AJAX 請求,例如從您的帳戶中提款!
處理
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
)。
組合全域和本機組態的規則通常是累加的,例如,所有全域和所有本機來源。對於那些只能接受單一值的屬性(例如 allowCredentials
和 maxAge
),本機值會覆寫全域值。請參閱 CorsConfiguration#combine(CorsConfiguration)
以取得更多詳細資訊。
若要從來源了解更多資訊或進行進階自訂,請參閱
|
具備憑證的請求
將 CORS 與具備憑證的請求搭配使用需要啟用 allowedCredentials
。請注意,此選項會與組態的網域建立高度信任,並透過公開敏感的使用者特定資訊(例如 Cookie 和 CSRF 令牌)來增加網路應用程式的攻擊面。
啟用憑證也會影響如何處理組態的 "*"
CORS 萬用字元
-
萬用字元在
allowOrigins
中未獲授權,但可以使用allowOriginPatterns
屬性來比對動態來源集。 -
當在
allowedHeaders
或allowedMethods
上設定時,Access-Control-Allow-Headers
和Access-Control-Allow-Methods
回應標頭會透過複製 CORS 預檢請求中指定的相關標頭和方法來處理。 -
當在
exposedHeaders
上設定時,Access-Control-Expose-Headers
回應標頭會設定為已組態的標頭清單或萬用字元。雖然 CORS 規範不允許在Access-Control-Allow-Credentials
設定為true
時使用萬用字元,但大多數瀏覽器都支援它,而且回應標頭並非在 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 組態來執行此操作。
依預設,全域組態啟用下列項目
-
所有來源。
-
所有標頭。
-
GET
、HEAD
和POST
方法。
allowCredentials
預設為未啟用,因為這會建立信任層級,進而公開敏感的使用者特定資訊(例如 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)
}