|
对于最新稳定版本,请使用 Spring Framework 7.0.6! |
跨域资源共享(CORS)
Spring WebFlux 允许你处理 CORS(跨域资源共享)。本节将介绍如何实现这一点。
< Introduction >
出于安全原因,浏览器禁止向当前源(origin)之外的资源发起 AJAX 请求。 例如,你可能在一个标签页中打开了你的银行账户页面,而在另一个标签页中打开了 evil.com。来自 evil.com 的脚本不应能够使用你的凭据向你的银行 API 发起 AJAX 请求——例如,从你的账户中提取资金!
处理
CORS 规范区分了预检请求(preflight)、简单请求(simple)和实际请求(actual)。 要了解 CORS 的工作原理,您可以阅读 这篇文章, 以及其他许多资料,或查阅规范以获取更多详细信息。
Spring WebFlux 的 HandlerMapping 实现提供了对 CORS 的内置支持。在成功将请求映射到处理器之后,HandlerMapping 会检查该请求和处理器对应的 CORS 配置,并执行后续操作。预检(Preflight)请求会被直接处理,而简单请求和实际的 CORS 请求则会被拦截、验证,并设置所需的 CORS 响应头。
为了启用跨域请求(即请求中包含 Origin 头部,且其值与请求的主机不同),您需要显式声明一些 CORS 配置。如果未找到匹配的 CORS 配置,预检(preflight)请求将被拒绝。简单 CORS 请求和实际 CORS 请求的响应中不会添加任何 CORS 相关头部,因此浏览器会拒绝这些请求。
每个 HandlerMapping 都可以
单独配置
基于 URL 模式的 CorsConfiguration 映射。在大多数情况下,应用程序使用 WebFlux 的 Java 配置来声明此类映射,从而生成一个全局的映射,并将其传递给所有的 HandlerMapping 实现。
你可以在 HandlerMapping 级别上结合全局 CORS 配置与更细粒度的处理器级别 CORS 配置。例如,带注解的控制器可以使用类级别或方法级别的 @CrossOrigin 注解(其他处理器可以实现 CorsConfigurationSource)。
全局配置与本地配置的组合规则通常是累加的——例如,所有全局来源和所有本地来源。对于仅能接受单个值的属性(如 allowCredentials 和 maxAge),本地配置将覆盖全局配置的值。详见 CorsConfiguration#combine(CorsConfiguration) 以获取更多详细信息。
|
要从源码中了解更多内容或进行高级自定义,请参阅:
|
凭据请求
在使用 CORS 进行带凭证的请求时,需要启用 allowedCredentials。请注意,此选项会与所配置的域名建立高度信任关系,同时也会通过暴露敏感的用户特定信息(例如 Cookie 和 CSRF Tokens)而扩大 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 Tokens),应仅在适当的情况下使用。启用该选项时,必须将 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 配置。您可以在任意 CorsConfiguration 上单独设置基于 URL 的 HandlerMapping 映射。然而,大多数应用程序使用 WebFlux Java 配置来实现这一点。
默认情况下,全局配置启用了以下内容:
-
所有源。
-
所有标头。
-
GET、HEAD和POST方法。
allowedCredentials 默认未启用,因为启用它会建立一种信任级别,暴露敏感的用户特定信息(例如 Cookie 和 CSRF Tokens),应仅在适当的情况下使用。启用该选项时,必须将 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 支持,
它与 函数式端点 非常契合。
如果你尝试在 Spring Security 中使用 CorsFilter,请记住 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)
}