对于最新的稳定版本,请使用 Spring Framework 7.0.6!spring-doc.cadn.net.cn

CORS

Spring WebFlux 允许你处理 CORS(跨域资源共享)。本节将介绍如何进行处理。spring-doc.cadn.net.cn

简介

出于安全原因,浏览器禁止向当前源之外的资源发起 AJAX 请求。例如,你可以在一个标签页中打开你的银行账户,在另一个标签页中打开 evil.com。evil.com 中的脚本不应该能够使用你的凭证向你的银行 API 发起 AJAX 请求——例如,从你的账户中提款!spring-doc.cadn.net.cn

跨源资源共享(CORS)是由W3C规范实现的,大多数浏览器都支持它,它允许你指定授权的跨域请求类型,而不是使用基于IFRAME或JSONP的不安全且功能较弱的变通方案。spring-doc.cadn.net.cn

处理

CORS规范区分了预检请求、简单请求和实际请求。 要了解CORS的工作原理,你可以阅读 这篇文章,或者查看规范以获取更多详细信息。spring-doc.cadn.net.cn

Spring WebFlux HandlerMapping 实现提供了内置的CORS支持。在成功将请求映射到处理器后,HandlerMapping 会检查给定请求和处理器的CORS配置并采取进一步操作。预检请求会被直接处理,而简单的和实际的CORS请求会被拦截、验证,并设置所需的CORS响应头。spring-doc.cadn.net.cn

为了启用跨域请求(即Origin标头存在且与请求的主机不同),你需要有一些明确声明的CORS配置。如果没有找到匹配的CORS配置,预检请求将被拒绝。不会向简单和实际的CORS请求的响应中添加任何CORS标头,因此浏览器会拒绝这些请求。spring-doc.cadn.net.cn

每个 HandlerMapping 可以通过 配置 基于 URL 模式的 CorsConfiguration 映射单独进行配置。在大多数情况下,应用程序 使用 WebFlux Java 配置来声明此类映射,这将导致传递给所有 HandlerMapping 实现的单一、全局映射。spring-doc.cadn.net.cn

你可以结合在 HandlerMapping 级别的全局CORS配置与更精细的、处理器级别的CORS配置。例如,注解控制器可以使用类级别或方法级别的 @CrossOrigin 注解(其他处理器可以实现 CorsConfigurationSource)。spring-doc.cadn.net.cn

全局和本地配置组合的规则通常是累加的——例如,所有全局和所有本地源。对于那些只能接受单个值的属性,如allowCredentialsmaxAge,本地值会覆盖全局值。有关更多详细信息,请参见 CorsConfiguration#combine(CorsConfiguration)spring-doc.cadn.net.cn

要从源代码中学习更多内容或进行高级自定义,请参见:spring-doc.cadn.net.cn

带凭证的请求

使用带有凭据的 CORS 请求需要启用 allowedCredentials。请注意,此选项会与配置的域名建立较高的信任级别,并且通过暴露如 cookies 和 CSRF Tokens等敏感用户信息,增加了网络应用程序的攻击面。spring-doc.cadn.net.cn

启用凭据也会影响配置的 "*" CORS 通配符的处理方式:spring-doc.cadn.net.cn

  • 通配符在<code>0</code>中不被授权,但可以使用<code>1</code>属性来匹配一组动态的来源。spring-doc.cadn.net.cn

  • 当设置为 allowedHeadersallowedMethods 时,Access-Control-Allow-HeadersAccess-Control-Allow-Methods 响应头由复制相关 在 CORS 预检请求中指定的头信息和方法来处理。spring-doc.cadn.net.cn

  • 当设置为 exposedHeaders 时,响应头被设置为配置的头列表或通配符。虽然 CORS 规范在 Access-Control-Allow-Credentials 设置为 true 时不允许使用通配符,但大多数浏览器支持它,并且在 CORS 处理期间无法获得所有响应头,因此无论 allowCredentials 属性的值如何,指定的通配符都是使用的头值。spring-doc.cadn.net.cn

虽然这样的通配符配置可能很方便,但建议尽可能配置一组有限的值,以提供更高层次的安全性。

@CrossOrigin

The @CrossOrigin 注解允许在注解的控制器方法上启用跨域请求,如下例所示:spring-doc.cadn.net.cn

@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 允许:spring-doc.cadn.net.cn

allowCredentials 默认情况下未启用,因为这会建立一个信任级别,可能会暴露敏感的用户特定信息(例如 cookies 和 CSRF Tokens),并且只能在适当的情况下使用。当启用时,必须将 allowOrigins 设置为一个或多个特定域名(但不能是特殊值 "*"),或者可以使用 allowOriginPatterns 属性来匹配动态的源列表。spring-doc.cadn.net.cn

maxAge 设置为 30 分钟。spring-doc.cadn.net.cn

@CrossOrigin 在类级别也是支持的,并且被所有方法继承。 以下示例指定了某个域并将 maxAge 设置为一小时:spring-doc.cadn.net.cn

@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,如下例所示:spring-doc.cadn.net.cn

@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配置来实现这一点。spring-doc.cadn.net.cn

默认的全局配置启用了以下功能:spring-doc.cadn.net.cn

allowedCredentials 默认情况下未启用,因为这会建立一个信任级别,可能会暴露敏感的用户特定信息(例如 cookies 和 CSRF Tokens),并且只能在适当的情况下使用。当启用时,必须将 allowOrigins 设置为一个或多个特定域名(但不能是特殊值 "*"),或者可以使用 allowOriginPatterns 属性来匹配动态的源列表。spring-doc.cadn.net.cn

maxAge 设置为 30 分钟。spring-doc.cadn.net.cn

要启用WebFlux中的CORS,您可以使用CorsRegistry回调,如下例所示:spring-doc.cadn.net.cn

@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-doc.cadn.net.cn

如果你尝试使用CorsFilter与Spring Security一起,需要注意的是Spring Security具有 内置支持 用于CORS。

要配置过滤器,您可以声明一个CorsWebFilter bean,并向其构造函数传递一个CorsConfigurationSource,如下例所示:spring-doc.cadn.net.cn

@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)
}