CORS

Web MVC 允许您处理跨源资源共享 (CORS,跨源资源共享)。本节描述了如何实现此功能。

介绍

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

跨源资源共享(CORS)是一个由 W3C 规范实现的 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 属性的值如何,指定时通配符字符都是使用的头值。

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

Processing

CORS 规范区分了预检请求、简单请求和实际请求。

要了解 CORS 的工作原理,您可以阅读 这篇文章,或者查看规范以获取更多细节。

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

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

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

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

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

要从源代码中了解更多信息或进行高级定制,请查看后台代码:

  • CorsConfiguration

  • CorsProcessor, DefaultCorsProcessor

  • AbstractHandlerMapping

@CrossOrigin

@CrossOrigin 注解在注释的控制器方法上启用跨源请求,如下例所示:

@RestController
@RequestMapping("/account")
public class AccountController {

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

  @DeleteMapping("/{id}")
  public void remove(@PathVariable Long id) {
    // ...
  }
}

默认情况下, @CrossOrigin 允许:

  • 所有来源。

  • 所有 Header。

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

allowCredentials 默认情况下未启用,因为它建立了一个信任级别,暴露了敏感的用户特定信息(如 cookies 和 CSRF 令牌), 应该只在适当的情况下使用。当它被启用时,要么 allowOrigins 必须设置为一个或多个特定域(但不能是特殊值 "*"), 要么可以使用 allowOriginPatterns 属性匹配到一组动态的来源。

maxAge 设置为 30 分钟。

@CrossOrigin 也支持在类级别,并被所有方法继承,如下例所示:

@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,如下例所示:

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

全局配置

除了细粒度的控制器方法级配置之外,你可能还想定义一些全局的 CORS 配置。你可以在任何 HandlerMapping 上单独设置基于 URL 的 CorsConfiguration 映射。然而,大多数应用程序使用 MVC Java 配置或 MVC XML 命名空间来完成这项任务。

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

  • 所有来源。

  • 所有标头。

  • GETHEADPOST 方法。

allowCredentials 默认情况下未启用,因为它建立了一个信任级别,暴露了敏感的用户特定信息(如 cookies 和 CSRF 令牌), 应该只在适当的情况下使用。当它被启用时,要么 allowOrigins 必须设置为一个或多个特定域(但不能是特殊值 "*"), 要么可以使用 allowOriginPatterns 属性匹配到一组动态的来源。

maxAge 设置为 30 分钟。

Java 配置

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

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

CORS 拦截器

你可以通过内置的 CorsInterceptor 应用 CORS 支持。

要配置过滤器,将 CorsConfigurationSource 传递给它的构造函数,如下例所示:

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