CORS 跨域资源共享

Spring MVC 允许您处理 CORS(跨域资源共享)。本节描述了如何实现。

介绍

出于安全原因,浏览器禁止对当前来源之外的资源进行 AJAX 调用。例如,您可能在一个标签中打开了您的银行账户,而在另一个标签中打开了 evil.com。来自 evil.com 的脚本不应该能够使用您的凭据向您的银行 API 发送 AJAX 请求,例如从您的账户中提取资金!

跨域资源共享(CORS)是由 W3C 规范 实现的,大多数浏览器都支持,它允许您指定授权的跨域请求类型,而不是使用基于 IFRAME 或 JSONP 的不太安全且不太强大的解决方法。

凭证请求

使用带凭证的 CORS 请求需要启用 allowedCredentials。请注意,此选项与配置的域建立了高度信任,并且通过公开敏感的用户特定信息(如 cookie 和 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 属性的值。

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

处理

CORS 规范区分预检请求、简单请求和实际请求。要了解 CORS 的工作原理,您可以阅读 这篇文章,以及其他许多文章,或查看规范以获取更多详细信息。

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

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

每个 HandlerMapping 可以单独使用基于 URL 模式的 CorsConfiguration 映射进行配置。在大多数情况下,应用程序使用 MVC Java 配置或 XML 命名空间声明此类映射,这将导致一个全局映射被传递给所有 HandlerMapping 实例。

您可以将全局 CORS 配置与更精细的处理程序级 CORS 配置结合在一起。例如,注解控制器可以使用类或方法级别的 @CrossOrigin 注解(其他处理程序可以实现 CorsConfigurationSource)。

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

要从源代码中了解更多信息或进行高级自定义,请查看以下代码:

  • CorsConfiguration

  • CorsProcessor, DefaultCorsProcessor

  • AbstractHandlerMapping

@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 Account retrieve(@PathVariable Long id) {
		// ...
	}

	@DeleteMapping("/{id}")
	public void remove(@PathVariable Long id) {
		// ...
	}
}
@RestController
@RequestMapping("/account")
class AccountController {

	@CrossOrigin
	@GetMapping("/{id}")
	fun retrieve(@PathVariable id: Long): Account {
		// ...
	}

	@DeleteMapping("/{id}")
	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 only be used 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 is inherited by all methods, as the following example shows:

  • Java

  • Kotlin

@CrossOrigin(origins = "https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {

	@GetMapping("/{id}")
	public Account retrieve(@PathVariable Long id) {
		// ...
	}

	@DeleteMapping("/{id}")
	public void remove(@PathVariable Long id) {
		// ...
	}
}
@CrossOrigin(origins = ["https://domain2.com"], maxAge = 3600)
@RestController
@RequestMapping("/account")
class AccountController {

	@GetMapping("/{id}")
	fun retrieve(@PathVariable id: Long): Account {
		// ...
	}

	@DeleteMapping("/{id}")
	fun remove(@PathVariable id: Long) {
		// ...
	}

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

  • Java

  • Kotlin

@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {

	@CrossOrigin("https://domain2.com")
	@GetMapping("/{id}")
	public Account retrieve(@PathVariable Long id) {
		// ...
	}

	@DeleteMapping("/{id}")
	public void remove(@PathVariable Long id) {
		// ...
	}
}
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
class AccountController {

	@CrossOrigin("https://domain2.com")
	@GetMapping("/{id}")
	fun retrieve(@PathVariable id: Long): Account {
		// ...
	}

	@DeleteMapping("/{id}")
	fun remove(@PathVariable id: Long) {
		// ...
	}
}

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 MVC Java configuration or the MVC XML namespace to do that.

By default, global configuration enables the following:

  • All origins.

  • All headers.

  • GET, HEAD, and POST methods.

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 only be used 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.

Java Configuration

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

  • Java

  • Kotlin

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

	@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
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

	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...
	}
}

XML Configuration

To enable CORS in the XML namespace, you can use the <mvc:cors> element, as the following example shows:

<mvc:cors>

	<mvc:mapping path="/api/**"
		allowed-origins="https://domain1.com, https://domain2.com"
		allowed-methods="GET, PUT"
		allowed-headers="header1, header2, header3"
		exposed-headers="header1, header2" allow-credentials="true"
		max-age="123" />

	<mvc:mapping path="/resources/**"
		allowed-origins="https://domain1.com" />

</mvc:cors>

CORS 过滤器

您可以通过内置的 CorsFilter 应用 CORS 支持。

如果您尝试在 Spring Security 中使用 CorsFilter,请记住 Spring Security 对 CORS 有内置支持
CorsConfigurationSource 传递给其构造函数,如以下示例所示:

  • Java

  • Kotlin

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

CorsFilter filter = new CorsFilter(source);
val config = CorsConfiguration()

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

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

val source = UrlBasedCorsConfigurationSource()
source.registerCorsConfiguration("/**", config)

val filter = CorsFilter(source)