CORS

Spring WebFlux 允许您处理 CORS(跨源资源共享)。本节介绍如何执行此操作。

Spring WebFlux lets you handle CORS (Cross-Origin Resource Sharing). This section describes how to do so.

Introduction

出于安全原因,浏览器禁止对当前来源外部的资源进行 AJAX 调用。例如,您可以在一个选项卡中拥有自己的银行账户,而在另一个选项卡中拥有 evil.com。来自 evil.com 的脚本不应该能够使用您的凭据向您的银行 API 发起 AJAX 请求——例如,从您的账户中取钱!

For security reasons, browsers prohibit AJAX calls to resources outside the current origin. For example, you could have your bank account in one tab and evil.com in another. Scripts from evil.com should not be able to make AJAX requests to your bank API with your credentials — for example, withdrawing money from your account!

跨源资源共享(CORS)是 most browsers实现的 W3C specification,它允许你指定授权的跨域请求类型,而不是使用基于 IFRAME 或 JSONP 的安全性较低且功能较弱的替代方法。

Cross-Origin Resource Sharing (CORS) is a W3C specification implemented by most browsers that lets you specify what kind of cross-domain requests are authorized, rather than using less secure and less powerful workarounds based on IFRAME or JSONP.

Processing

CORS 规范将预检请求、简单请求和实际请求区别开来。若要了解 CORS 的工作原理,可以阅读 this article,以及众多其他文章,或查阅规范以了解更多详情。

The CORS specification distinguishes between preflight, simple, and actual requests. To learn how CORS works, you can read this article, among many others, or see the specification for more details.

Spring WebFlux HandlerMapping 实现提供内置的 CORS 支持。在成功将请求映射到某个处理程序后,HandlerMapping 将检查给定请求和处理程序的 CORS 配置并执行进一步的操作。预检请求将直接处理,而简单和实际的 CORS 请求将被截取、验证,并设置必需的 CORS 响应头。

Spring WebFlux HandlerMapping implementations provide built-in support for CORS. After successfully mapping a request to a handler, a HandlerMapping checks the CORS configuration for the given request and handler and takes further actions. Preflight requests are handled directly, while simple and actual CORS requests are intercepted, validated, and have the required CORS response headers set.

为了启用跨源请求(即,Origin 标头存在,并且与请求的主机不同),你需要拥有一些明确声明的 CORS 配置。如果没有找到匹配的 CORS 配置,则会拒绝预检请求。不会向简单和实际 CORS 请求的响应添加任何 CORS 标头,因此浏览器会拒绝它们。

In order to enable cross-origin requests (that is, the Origin header is present and differs from the host of the request), you need to have some explicitly declared CORS configuration. If no matching CORS configuration is found, preflight requests are rejected. No CORS headers are added to the responses of simple and actual CORS requests and, consequently, browsers reject them.

每个 HandlerMapping 均可以与基于 URL 模式的 CorsConfiguration 映射一起单独https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/reactive/handler/AbstractHandlerMapping.html#setCorsConfigurations-java.util.Map-[配置]。在大多数情况下,应用程序使用该 WebFlux Java 配置来申明此类映射,其结果是将一个全局映射传递给所有 HandlerMapping 实现。

Each HandlerMapping can be configured individually with URL pattern-based CorsConfiguration mappings. In most cases, applications use the WebFlux Java configuration to declare such mappings, which results in a single, global map passed to all HandlerMapping implementations.

你可以将 HandlerMapping 级的全局 CORS 配置与更细粒度的处理程序级 CORS 配置结合起来。例如,带注释的控制器可以使用类级或方法级的 @CrossOrigin 注释(其他处理程序可以实现`CorsConfigurationSource`)。

You can combine global CORS configuration at the HandlerMapping level with more fine-grained, handler-level CORS configuration. For example, annotated controllers can use class- or method-level @CrossOrigin annotations (other handlers can implement CorsConfigurationSource).

组合全局和局部配置的规则通常是累加的,例如,所有全局和所有局部来源。对于只能接受单个值的那些属性,例如 allowCredentialsmaxAge,则局部值覆盖全局值。有关更多详情,请参见https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/cors/CorsConfiguration.html#combine-org.springframework.web.cors.CorsConfiguration-[CorsConfiguration#combine(CorsConfiguration)]。

The rules for combining global and local configuration are generally additive — for example, all global and all local origins. For those attributes where only a single value can be accepted, such as allowCredentials and maxAge, the local overrides the global value. See CorsConfiguration#combine(CorsConfiguration) for more details.

要从源代码了解详细信息或进行高级自定义,请参阅:

To learn more from the source or to make advanced customizations, see:

  • CorsConfiguration

  • CorsProcessor and DefaultCorsProcessor

  • AbstractHandlerMapping

Credentialed Requests

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

Using CORS with credentialed requests requires enabling allowedCredentials. Be aware that this option establishes a high level of trust with the configured domains and also increases the surface of attack of the web application by exposing sensitive user-specific information such as cookies and CSRF tokens.

启用凭据还会影响处理已配置的 "*" CORS 通配符的方式:

Enabling credentials also impacts how the configured "*" CORS wildcards are processed:

  • Wildcards are not authorized in allowOrigins, but alternatively the allowOriginPatterns property may be used to match to a dynamic set of origins.

  • When set on allowedHeaders or allowedMethods, the Access-Control-Allow-Headers and Access-Control-Allow-Methods response headers are handled by copying the related headers and method specified in the CORS preflight request.

  • When set on exposedHeaders, Access-Control-Expose-Headers response header is set either to the configured list of headers or to the wildcard character. While the CORS spec does not allow the wildcard character when Access-Control-Allow-Credentials is set to true, most browsers support it and the response headers are not all available during the CORS processing, so as a consequence the wildcard character is the header value used when specified regardless of the value of the allowCredentials property.

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

While such wildcard configuration can be handy, it is recommended when possible to configure a finite set of values instead to provide a higher level of security.

@CrossOrigin

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

默认情况下,@CrossOrigin 允许:

By default, @CrossOrigin allows:

  • All origins.

  • All headers.

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

默认情况下不会启用 allowCredentials,因为它建立了一个信任级别,会公开敏感用户特定信息(例如 cookie 和 CSRF 令牌),应该仅在适当的情况下使用。当启用它时,allowOrigins 必须被设置为一个或多个特定域(但不是特殊值 "*"),或者也可以使用 allowOriginPatterns 属性与一组动态来源匹配。

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 设置为 30 分钟。

maxAge is set to 30 minutes.

@CrossOrigin 也在类级别受支持,并由所有方法继承。以下示例指定了某个域,并将 maxAge 设置为一小时:

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

您可以同时在类级别和方法级别使用 @CrossOrigin,如下例所示:

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

Java
@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.
Kotlin
@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) {
		// ...
	}
}
3 Using @CrossOrigin at the class level.
4 Using @CrossOrigin at the method level.

Global Configuration

除了细粒度的控制器方法级别配置之外,您可能还希望定义一些全局 CORS 配置。您可以在任何 HandlerMapping 上分别设置基于 URL 的 CorsConfiguration 映射。但是,大多数应用程序使用 WebFlux Java 配置来执行此操作。

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,因为它建立了一个信任级别,会公开敏感用户特定信息(例如 cookie 和 CSRF 令牌),应该仅在适当的情况下使用。当启用它时,allowOrigins 必须被设置为一个或多个特定域(但不是特殊值 "*"),或者也可以使用 allowOriginPatterns 属性与一组动态来源匹配。

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 设置为 30 分钟。

maxAge is set to 30 minutes.

要在 WebFlux Java 配置中启用 CORS,可以使用 CorsRegistry 回调,如下例所示:

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

您可以通过内置https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/cors/reactive/CorsWebFilter.html[CorsWebFilter]应用 CORS 支持,它与 functional endpoints配合良好。

You can apply CORS support through the built-in CorsWebFilter, which is a good fit with webflux-fn.

如果您尝试将 CorsFilter 与 Spring Security 一起使用,请记住 Spring Security 拥有用于 CORS 的 内置支持

If you try to use the CorsFilter with Spring Security, keep in mind that Spring Security has built-in support for CORS.

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

To configure the filter, you can declare a CorsWebFilter bean and pass a CorsConfigurationSource to its constructor, as the following example shows:

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