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

以下示例展示了如何使用 @CrossOrigin 注释在带注释的控制器方法上启用跨域请求

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

默认情况下,@CrossOrigin 允许

  • 所有来源。

  • 所有标头。

  • 控制器方法映射到的所有 HTTP 方法。

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

maxAge 设置为 30 分钟。

@CrossOrigin 也支持在类级别使用,并且会继承到所有方法,如下例所示

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

您可以像以下示例所示那样在类级别和方法级别都使用 @CrossOrigin

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

全局配置

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

默认情况下,全局配置启用以下内容

  • 所有来源。

  • 所有标头。

  • GETHEADPOST 方法。

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

maxAge 设置为 30 分钟。

Java 配置

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

  • 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 配置

要在 XML 命名空间中启用 CORS,您可以使用 `<mvc:cors>` 元素,如下例所示

<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 支持。

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

要配置过滤器,请向其构造函数传递一个 `CorsConfigurationSource`,如下例所示

  • Java

  • Kotlin

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

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

// Possibly...
// config.applyPermitDefaultValues()

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

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

val filter = CorsFilter(source)