CORS

Spring MVC 允许你处理 CORS(跨源资源共享)。此部分介绍如何处理。

Introduction

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

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

Credentialed Requests

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

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

  • 通配符在 allowOrigins 中不被授权,但可以另行使用 allowOriginPatterns 属性来匹配一组动态来源。

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

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

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

Processing

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

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

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

每个 HandlerMapping 均可以与基于 URL 模式的 CorsConfiguration 映射一起单独https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/handler/AbstractHandlerMapping.html#setCorsConfigurations-java.util.Map-[配置]。在大多数情况下,应用程序使用 MVC Java 配置或 XML 命名空间来声明此类映射,这会将一个全局映射传递给所有 HandlerMapping 实例。

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

要从源学习更多内容或进行高级自定义,请查看以下代码:

  • 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 允许:

  • All origins.

  • All headers.

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

由于这建立了一个暴露敏感用户特定信息(例如 cookie 和 CSRF 令牌)的信任级别,因此默认情况下未启用 allowCredentials,并且仅应在适当的情况下使用。当它被启用时,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) {
		// ...
	}
}

Global Configuration

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

默认情况下,全局配置启用以下功能:

  • All origins.

  • All headers.

  • GETHEADPOST 方法。

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

maxAge 设置为 30 分钟。

Java Configuration

要在 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 Configuration

要在 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 Filter

您可以通过内置功能应用 CORS 支持https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/CorsFilter.html[CorsFilter]。

如果您尝试将 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)