在 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