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)。

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

要从源头了解更多信息或进行高级定制,请参阅:

  • CorsConfiguration

  • CorsProcessorDefaultCorsProcessor

  • AbstractHandlerMapping

凭证请求

使用带凭证请求的CORS需要启用allowedCredentials。请注意,此选项与配置的域建立了高度信任,并通过公开敏感的用户特定信息(如cookies和CSRF令牌)增加了Web应用程序的攻击面。

启用凭证还会影响如何处理配置的"*" CORS通配符:

  • 通配符在allowOrigins中不被授权,但可以使用allowOriginPatterns属性来匹配动态集合的来源。

  • 当设置在allowedHeadersallowedMethods上时,Access-Control-Allow-HeadersAccess-Control-Allow-Methods响应头通过复制CORS预检请求中指定的相关头部和方法来处理。

  • 当设置在exposedHeaders上时,Access-Control-Expose-Headers响应头设置为配置的头部列表或通配符字符。虽然CORS规范不允许在Access-Control-Allow-Credentials设置为true时使用通配符字符,但大多数浏览器支持它,并且在CORS处理期间并非所有响应头都可用,因此无论allowCredentials属性的值如何,指定时都会使用通配符字符作为头部值。

虽然这种通配符配置可能很方便,但建议在可能的情况下配置有限的值,以提供更高级别的安全性。

@CrossOrigin

The @CrossOrigin annotation enables cross-origin requests on annotated controller methods, as the following example shows:

  • 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) {
		// ...
	}
}

By default, @CrossOrigin allows:

  • All origins.

  • All headers.

  • All HTTP methods to which the controller method is mapped.

allowCredentials is not enabled by default, since that establishes a trust level that exposes sensitive user-specific information (such as cookies and CSRF tokens) and should be used only where appropriate. When it is enabled either allowOrigins must be set to one or more specific domain (but not the special value "*") or alternatively the allowOriginPatterns property may be used to match to a dynamic set of origins.

maxAge is set to 30 minutes.

@CrossOrigin is supported at the class level, too, and inherited by all methods. The following example specifies a certain domain and sets maxAge to an hour:

  • 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) {
		// ...
	}
}

You can use @CrossOrigin at both the class and the method level, as the following example shows:

  • 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 Using @CrossOrigin at the class level.
2 Using @CrossOrigin at the method level.
@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 Using @CrossOrigin at the class level.
2 Using @CrossOrigin at the method level.

Global Configuration

In addition to fine-grained, controller method-level configuration, you probably want to define some global CORS configuration, too. You can set URL-based CorsConfiguration mappings individually on any HandlerMapping. Most applications, however, use the WebFlux Java configuration to do that.

By default global configuration enables the following:

  • All origins.

  • All headers.

  • GET, HEAD, and POST methods.

allowedCredentials is not enabled by default, since that establishes a trust level that exposes sensitive user-specific information (such as cookies and CSRF tokens) and should be used only where appropriate. When it is enabled either allowOrigins must be set to one or more specific domain (but not the special value "*") or alternatively the allowOriginPatterns property may be used to match to a dynamic set of origins.

maxAge is set to 30 minutes.

To enable CORS in the WebFlux Java configuration, you can use the CorsRegistry callback, as the following example shows:

  • 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();

	// 可能...
	// 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()

	// 可能...
	// config.applyPermitDefaultValues()

	config.allowCredentials = true
	config.addAllowedOrigin("https://domain1.com")
	config.addAllowedHeader("*")
	config.addAllowedMethod("*")

	val source = UrlBasedCorsConfigurationSource().apply {
		registerCorsConfiguration("/**", config)
	}
	return CorsWebFilter(source)
}