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 令牌)来增加 Web 应用程序的攻击面。
启用凭据还会影响已配置的"*"
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
方法。
默认情况下不会启用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)
}