在 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。我们可以通过指定以下注解属性的值来自定义配置:origins、methods、allowedHeaders、exposedHeaders、allowCredentials 或 maxAge。
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 数组。如果我们需要在运行时从外部加载这个数组,这将非常有用。
此外,我们还可以使用 allowedMethods、allowedHeaders、exposedHeaders、maxAge 和 allowCredentials 来设置响应头和自定义选项。例如,我们可以通过在上述配置中添加 allowedMethods("*") 对所有 HTTP 方法开放 CORS。
值得注意的是,自 2.4.0 版起,Spring Boot 除了引入 allowedOrigins 之外,还引入了 allowedOriginPatterns。这个新元素为定义 pattern 提供了更大的灵活性。此外,当 allowCredentials 为 true 时,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