|
对于最新的稳定版本,请使用 Spring Framework 7.0.6! |
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)。
全局和本地配置组合的规则通常是累加的——例如,所有全局和所有本地源。对于那些只能接受单个值的属性,如allowCredentials和maxAge,本地值会覆盖全局值。有关更多详细信息,请参见
CorsConfiguration#combine(CorsConfiguration)。
|
要从源代码中学习更多内容或进行高级自定义,请参见:
|
带凭证的请求
使用带有凭据的 CORS 请求需要启用 allowedCredentials。请注意,此选项会与配置的域名建立较高的信任级别,并且通过暴露如 cookies 和 CSRF Tokens等敏感用户信息,增加了网络应用程序的攻击面。
启用凭据也会影响配置的 "*" CORS 通配符的处理方式:
-
通配符在<code>0</code>中不被授权,但可以使用<code>1</code>属性来匹配一组动态的来源。
-
当设置为
allowedHeaders或allowedMethods时,Access-Control-Allow-Headers和Access-Control-Allow-Methods响应头由复制相关 在 CORS 预检请求中指定的头信息和方法来处理。 -
当设置为
exposedHeaders时,响应头被设置为配置的头列表或通配符。虽然 CORS 规范在Access-Control-Allow-Credentials设置为true时不允许使用通配符,但大多数浏览器支持它,并且在 CORS 处理期间无法获得所有响应头,因此无论allowCredentials属性的值如何,指定的通配符都是使用的头值。
| 虽然这样的通配符配置可能很方便,但建议尽可能配置一组有限的值,以提供更高层次的安全性。 |
@CrossOrigin
The @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 默认情况下未启用,因为这会建立一个信任级别,可能会暴露敏感的用户特定信息(例如 cookies 和 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配置。你可以为任何HandlerMapping单独设置基于URL的CorsConfiguration映射。然而,大多数应用程序使用WebFlux Java配置来实现这一点。
默认的全局配置启用了以下功能:
-
所有源。
-
所有头部。
-
GET,HEAD, 和POST方法。
allowedCredentials 默认情况下未启用,因为这会建立一个信任级别,可能会暴露敏感的用户特定信息(例如 cookies 和 CSRF Tokens),并且只能在适当的情况下使用。当启用时,必须将 allowOrigins 设置为一个或多个特定域名(但不能是特殊值 "*"),或者可以使用 allowOriginPatterns 属性来匹配动态的源列表。
maxAge 设置为 30 分钟。
要启用WebFlux中的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)
}