在 Spring 应用中处理 CORS 跨域

1、概览

在任何现代浏览器中,随着通过 REST API 获取数据的 HTML5 和 JS 客户端的出现,跨源资源共享 (CORS) 已成为一种相关规范。

通常,提供 JS 的主机(如 example.com)与提供数据的主机(如 api.example.com)不同。在这种情况下,CORS 可以实现跨域通信。

Spring 为 CORS 提供了一流的 支持,为在任何 Spring 或 Spring Boot Web 应用中配置 CORS 提供了简单而强大的方法。

2、Controller 方法级的 CORS 配置

启用 CORS 非常简单,只需添加注解 @CrossOrigin

我们可以通过几种不同的方式来实现。

2.1、在 @RequestMapping 方法上注解 @CrossOrigin

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

    @CrossOrigin
    @RequestMapping(method = RequestMethod.GET, path = "/{id}")
    public Account retrieve(@PathVariable Long id) {
        // ...
    }

    @RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}

在上面的示例中,我们只为 retrieve() 方法启用了 CORS。我们可以看到,我们没有为 @CrossOrigin 注解设置任何配置,因此它使用的是默认值:

  • 允许所有 origin。
  • 允许使用的 HTTP 方法是 @RequestMapping 注解(本例中为 GET)中指定的方法。
  • 缓存预检响应的时间(maxAge)为 30 分钟。

2.2、在 Controller 类上注解 @CrossOrigin

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

    @RequestMapping(method = RequestMethod.GET, path = "/{id}")
    public Account retrieve(@PathVariable Long id) {
        // ...
    }

    @RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}

这次,我们在类级别上添加了 @CrossOrigin。因此,retrieve()remove() 方法都启用了 @CrossOrigin。我们可以通过指定以下注解属性的值来自定义配置:originsmethodsallowedHeadersexposedHeadersallowCredentialsmaxAge

2.3、在 Controller 类上和 hanlder 方法上注解 @CrossOrigin

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

    @CrossOrigin("http://example.com")
    @RequestMapping(method = RequestMethod.GET, "/{id}")
    public Account retrieve(@PathVariable Long id) {
        // ...
    }

    @RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}

Spring 将合并两个注解的属性,创建合并的 CORS 配置。

这里,两个方法的 maxAge 都是 3600 秒,remove() 方法允许所有 origin,而 retrieve() 方法只允许来自 http://example.com 的 origin。

3、全局 CORS 配置

Spring 允许我们在 controller 中定义全局 CORS 配置,作为基于注解的细粒度配置的替代方案。这与使用基于 Filter 的解决方案类似,但可以在 Spring MVC 中声明,并与细粒度的 @CrossOrigin 配置相结合。

默认情况下,允许所有 origin 以及 GET、HEAD 和 POST 方法。

3.1、 Java 配置

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**");
    }
}

上面的示例允许从任何 origin 向应用中的任何端点发出 CORS 请求。

为了进一步细化控制,registry.addMapping 方法会返回一个 CorsRegistration 对象,我们可以使用该对象进行其他配置。还有一个 allowedOrigins 方法可以让我们指定一个允许的 origin 数组。如果我们需要在运行时从外部加载这个数组,这将非常有用。

此外,我们还可以使用 allowedMethodsallowedHeadersexposedHeadersmaxAgeallowCredentials 来设置响应头和自定义选项。例如,我们可以通过在上述配置中添加 allowedMethods("*") 对所有 HTTP 方法开放 CORS。

值得注意的是,自 2.4.0 版起,Spring Boot 除了引入 allowedOrigins 之外,还引入了 allowedOriginPatterns。这个新元素为定义 pattern 提供了更大的灵活性。此外,当 allowCredentialstrue 时,allowedOrigins 不能包含特殊值 "*",因为该值不能在 Access-Control-Allow-Origin 响应头中设置。要解决这个问题并允许一组 origin 的凭证,我们可以明确列出它们,或者考虑使用 allowedOriginPatterns 来代替。

3.2、XML 命名空间

启用最基本 CORS 的 XML 配置如下,通过 path 属性指定路径模式,其默认属性与 Java 配置相同:

<mvc:cors>
    <mvc:mapping path="/**" />
</mvc:cors>

还可以使用自定义属性声明多个 CORS mapping:

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

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

</mvc:cors>

4、在 Spring Security 中配置 CORS

如果我们在项目中使用 Spring Security,我们必须采取额外的步骤确保它与 CORS 协作良好。这是因为 CORS 需要首先处理。否则,Spring Security 会在请求到达 Spring MVC 之前将其拒绝。

幸运的是,Spring Security 提供了开箱即用的解决方案:

@EnableWebSecurity
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.cors().and()...
    }
}

我们可以配置 CORS 以覆盖默认的 Spring Security CORS 处理器。为此,我们需要添加一个 CorsConfigurationSource Bean,使用 CorsConfiguration 实例来处理 CORS 配置。如果添加了 corsFilter Bean,http.cors() 方法就会使用 CorsFilter,否则就会使用 CorsConfigurationSource。如果两者都未配置,则使用 Spring MVC pattern inspector handler。

让我们将 CorsConfigurationSource Bean 添加到 WebSecurityConfig 类中:

@Bean
CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(Arrays.asList("*"));
    configuration.setAllowedMethods(Arrays.asList("*"));
    configuration.setAllowedHeaders(Arrays.asList("*"));
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}

在此,我们使用默认构造函数创建一个 CorsConfiguration 实例,然后设置允许的 origin、允许的方法和响应头。通过上述配置,我们可以从任何 origin、任何方法和任何请求头向应用中的任何端点发出 CORS 请求。最后,我们将其作为参数传递给 UrlBasedCorsConfigurationSource 实例,并将其返回。

5、工作原理

CORS 请求会自动调度给各种已注册的 HandlerMapping。它们处理 CORS 预检请求,并使用 CorsProcessor 实现(默认为 DefaultCorsProcessor)拦截 CORS 简单和实际请求,以添加相关的 CORS 响应头(如 Access-Control-Allow-Origin)。

CorsConfiguration 允许我们指定如何处理 CORS 请求,包括允许的 origin、header 和方法等。我们可以通过多种方式提供它:

  • 通过 AbstractHandlerMapping#setCorsConfiguration(),我们可以指定一个映射到路径模式(如 /api/**)上的 Map,其中包含多个 CorsConfiguration
  • 子类可以通过覆盖 AbstractHandlerMapping#getCorsConfiguration(Object, HttpServletRequest) 方法来提供自己的 CorsConfiguration
  • Handler 可以实现 CorsConfigurationSource 接口(就像现在的 ResourceHttpRequestHandler 一样),为每个请求提供一个 CorsConfiguration

6、总结

在本文中,我们展示了 Spring 如何为在应用程序中启用 CORS 提供支持。

我们从 Controller 的配置开始。我们看到,只需添加注解 @CrossOrigin,即可在一个特定方法或整个 Controller 中启用 CORS。

此外,我们还了解到,要实现在 Controller 之外控制 CORS 配置,我们可以使用 Java 配置或 XML 配置。


参考:https://www.baeldung.com/spring-cors