Spring 配置 CORS 跨域资源共享的原理与实战

1 CORS 简介

跨域资源共享 (CORS,Cross-Origin Resource Sharing) 是一种网络安全机制,它允许浏览器在跨域请求时访问其他域名下的资源。

在网络应用中,有时需要从不同的域名加载资源,例如从图床请求图片、从 CDN 请求 CSS 和 JS 等。为了保障安全,浏览器会执行同源策略 (Same-Origin Policy) 来限制不同源之间的资源访问。然而,CORS 机制可以合法地打破这种限制。

CORS 定义了一种用于检查服务器是否允许跨域的预检请求 (Preflight Request)。在执行跨域访问前,会先向目标 URL 发送一次 OPTIONS 预检请求,并携带以下三个请求头:

  • Origin:该头部的值为当前页面所在的域名,用于告诉服务器当前请求的来源。如果没有这个头部,服务器将不会进行 CORS 验证。
  • Access-Control-Request-Method:该头部的值为实际请求将会使用的方法。
  • Access-Control-Request-Headers:该头部的值为实际请求将会使用的 header 集合。

如果服务端的 CORS 验证失败,将返回 4xx 错误码;如果验证成功,则返回 200 状态码,并携带以下响应头:

  • Access-Control-Allow-Origin:允许请求的域,通常为预检请求中的 Origin 的值。
  • Access-Control-Allow-Credentials:指示是否允许携带认证信息(例如 Cookies)进行跨域请求。
  • Access-Control-Allow-Methods:指定允许的 HTTP 方法,如 GETPOSTPUTDELETE 等。
  • Access-Control-Allow-Headers:实际请求中可以出现的 header 集合。
  • Access-Control-Max-Age:预检请求的结果缓存时间,以避免每次请求都进行预检。

根据预检请求的响应,浏览器会决定是否发送实际请求。

2 Spring MVC 配置 CORS

2.1 全局配置

2.1.1 通过创建 CorsFilter Bean 配置

Spring MVC 中提供了一个名为 CorsFilter 的过滤器。我们可以通过创建一个 CorsFilter 的 Bean 来配置它:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Configuration
public class CORSConfiguration {
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.addAllowedOrigin("http://cors1.example.com");
        configuration.addAllowedMethod("GET");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/hello", configuration);
        return new CorsFilter(source);
    }
}
  • 在以上示例中,我们设置了对 URL 资源 /hello 只允许通过 GET 方法从 http://cors1.example.com 域进行跨域请求。

然后在 Postman 中发送如下请求:

1
2
3
4
OPTIONS /hello HTTP/1.1
Host: localhost:8080
Access-Control-Request-Method: POST
Origin: http://cors1.example.com

这里我们向一个不允许跨域的 POST 方法发送了一个预检请求,跟设想的一样,服务器返回了 403 错误:

1
Invalid CORS request

如果将 Access-Control-Request-Method 改为正确的 GET 方法:

1
2
3
4
OPTIONS /hello HTTP/1.1
Host: localhost:8080
Access-Control-Request-Method: GET
Origin: http://cors1.example.com

服务器成功返回了 200 状态码,同时携带了以下响应头:

1
2
Access-Control-Allow-Origin: http://cors1.example.com
Access-Control-Allow-Methods: GET

2.1.2 实现 WebMvcConfigurer 的 addCorsMappings() 方法

WebMvcConfigurer 是 Spring MVC 中的一个接口。我们可以通过实现其中的 addCorsMappings 方法,为全局 API 配置 CORS 规则:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/hello")
                .allowedOrigins("http://cors2.example.com")
                .allowedMethods("GET");
    }
}
  • 在以上示例中,我们设置了对 URL 资源 /hello 只允许通过 GET 方法从 http://cors2.example.com 域进行跨域请求。
注意

在 Spring MVC 中,实现 WebMvcConfigurer.addCorsMappings() 方法和注入 CorsFilter 是等效的,设置其一即可。如果这两种方法同时配置,那么注入 CorsFilter 的优先级更高。

我们做如下测试:

  1. CorsFilter 注入方式设置 allowedOriginshttp://cors1.example.com
  2. 实现 WebMvcConfigurer.addCorsMappings() 方法设置 allowedOriginshttp://cors2.example.com

然后将预检请求头中的 Origin 设置为 http://cors2.example.com,发现请求被拒,而 http://cors1.example.com 则成功通过校验。

下文均通过这种方式实验,因而省略实验内容。

2.2 局部配置

2.2.1 使用 @CrossOrigin 注解

我们可以在特定 HTTP 端点上使用 Spring MVC 提供的 @CrossOrigin@CrossOrigin 注解提供了一些属性,以定制跨域配置:

  • origins:指定允许哪些源(域名)访问资源,默认为任何域名都可以访问("*")。
  • methods:指定允许的 HTTP 方法,如 GETPOSTPUTDELETE 等,默认为所有 HTTP 方法都允许。
  • allowedHeaders:指定允许的请求头,默认为所有请求头都允许。
  • exposedHeaders:指定允许浏览器访问的响应头,默认为空。
  • allowCredentials:指示是否允许携带认证信息(如 Cookies)进行跨域请求,默认为 false。

例如,在 Spring 控制器中,可以使用 @CrossOrigin 注解实现局部跨域配置:

1
2
3
4
5
6
7
8
@RestController
public class MyController {
    @CrossOrigin(origins = "http://cors3.example.com", methods = RequestMethod.GET)
    @GetMapping("/hello")
    public String hello() {
        return "Hello, CORS!";
    }
}
  • 在以上示例中,我们直接在 /hello 端点上设置了只允许通过 GET 方法从 http://cors3.example.com 域进行跨域请求。
注意
  • @CrossOrigin 注解可以与 WebMvcConfigureraddCorsMappings() 方法同时进行配置,且它们能够共同生效于同一资源。
  • 如果注入了 CorsFilter,则另外两种配置都会被覆盖。

3 Spring Security 配置

3.1 引入 Security 情况下使能 MVC 配置

在引入 Spring Security 后,之前介绍的 Spring MVC 的 CORS 配置方案将失效,表现为每次预检请求都会返回 401 状态码(原因是预检请求被 Spring Security 当作未认证的请求进行了过滤)。为了使 Spring MVC 的 CORS 正常工作,需要在 Spring Security 中调用 HttpSecurity 工具类中的 CorsConfigurer<HttpSecurity> cors() 方法:

1
2
3
4
5
6
7
8
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.cors()
    .and().authorizeRequests()
    .anyRequest().authenticated()
    .and().formLogin()
    .and().httpBasic();
}

在调用该方法之后,Spring MVC 中的跨域配置就可以生效了,且上文介绍的三种配置方案之间的优先级关系与不引入 Spring Security 时相同。

3.2 HttpSecurity 配置 CorsConfigurationSource

除了调用 CorsConfigurer<HttpSecurity> cors(),我们还可以调用 HttpSecurity 工具类中的重载方法 HttpSecurity cors(Customizer<CorsConfigurer<HttpSecurity>> corsCustomizer) 直接配置跨域:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors(c -> {
            CorsConfigurationSource source = request -> {
                CorsConfiguration config = new CorsConfiguration();
                config.setAllowedOrigins(List.of("http://cors4.example.com"));
                config.setAllowedMethods(List.of("GET"));
                return config;
            };
            c.configurationSource(source);
        })

        .authorizeRequests()
        .anyRequest().authenticated()
        .and().formLogin()
        .and().httpBasic();
    }
}
  • 在以上示例中,我们设置了所有资源只允许通过 GET 方法从 http://cors4.example.com 域进行跨域请求。
注意
HttpSecurity 配置的 CORS 策略会覆盖所有的 Spring MVC 配置。

3.3 通过创建 CorsConfigurationSource Bean 配置

创建 CorsConfigurationSource Bean,同样可以配置 CORS 策略:

1
2
3
4
5
6
7
8
9
@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.addAllowedOrigin("http://cors5.example.com");
    configuration.addAllowedMethod("GET");
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/hello", configuration);
    return source;
}
  • 在以上示例中,我们设置了对 URL 资源 /hello 只允许通过 GET 方法从 http://cors5.example.com 域进行跨域请求。
注意
  • 注入 CorsConfigurationSource 配置的 CORS 策略同样会覆盖所有的 Spring MVC 配置。
  • 通过 HttpSecurity 配置的 CORS 策略优先级要高于创建 CorsConfigurationSource Bean


欢迎关注我的公众号,第一时间获取文章更新:

微信公众号

相关内容