CORS в Spring Web

CORS в Spring Web

Alexander Kosarev

В предыдущей статье я постарался вкратце объяснить, что такое протокол CORS и как он работает. В этой статье я предлагаю разобраться с настройками CORS на стороне Spring Web, Spring Security и Spring Boot.

Настройка CORS в Spring Web

За поддержку CORS на стороне веб-приложений, основанных на Servlet API, в экосистеме Spring отвечает именно Spring Web — фильтр CorsFilter (не путать с CorsFilter из Catalina! и все необходимые для его настройки классы и интерфейсы находятся именно в этом модуле, а не в Spring Security. Поэтому для включения поддержки CORS в вашем веб-приложении на основе Spring вам не потребуются дополнительные зависимости.

Всё, что нужно для настройки CORS — зарегистрировать в контексте приложения компонент CorsFilter:

@Configuration
class WebBeans {

  @Bean
  CorsFilter corsFilter() {
    // Источник конфигураций CORS
    var corsConfigurationSource = new UrlBasedCorsConfigurationSource();
    // Конфигурация CORS
    var globalCorsConfiguration = new CorsConfiguration();

    // Разрешаются CORS-запросы:
    // - с сайта http://localhost:8080
    globalCorsConfiguration.addAllowedOrigin("http://localhost:8080");
    // - с нестандартными заголовками Authorization и X-CUSTOM-HEADER
    globalCorsConfiguration.addAllowedHeader(HttpHeaders.AUTHORIZATION);
    globalCorsConfiguration.addAllowedHeader("X-CUSTOM-HEADER");
    // - с передачей учётных данных
    globalCorsConfiguration.setAllowCredentials(true);
    // - с методами GET, POST, PUT, PATCH и DELETE
    globalCorsConfiguration.setAllowedMethods(List.of(
        HttpMethod.GET.name(),
        HttpMethod.POST.name(),
        HttpMethod.PUT.name(),
        HttpMethod.PATCH.name(),
        HttpMethod.DELETE.name()
    ));
    // JavaScript может обращаться к заголовку X-OTHER-CUSTOM-HEADER ответа
    globalCorsConfiguration.setExposedHeaders(List.of("X-OTHER-CUSTOM-HEADER"));
    // Браузер может кешировать настройки CORS на 10 секунд
    globalCorsConfiguration.setMaxAge(Duration.ofSeconds(10));

    // Использование конфигурации CORS для всех запросов
    corsConfigurationSource.registerCorsConfiguration("/**", globalCorsConfiguration);

    return new CorsFilter(corsConfigurationSource);
  }
}

Основные компоненты, используемые для настройки CORS: CorsConfigurationSource и CorsConfiguration.

CorsConfigurationSource

Интерфейс CorsConfigurationSource объявляет метод getCorsConfiguration, который возвращает настройки CORS, наиболее подходящие для полученного запроса.

Основная реализация данного интерфейса — это класс UrlBasedCorsConfigurationSource, который может хранить множество настроек CORS и определяет наиболее подходящие по URL запроса.

Для регистрации настроек CORS в классе UrlBasedCorsConfigurationSource есть метод registerCorsConfiguration, который в качестве аргумента принимает шаблон URL и связанные с ним настройки.

CorsConfiguration

Класс CorsConfiguration описывает настройки CORS и содержит следующие свойства:

  • allowedOrigins — список разрешённых адресов, с которых могут выполняться CORS-запросы
  • allowedMethods — список разрешённых методов
  • allowedHeaders — список разрешённых заголовков
  • exposedHeaders — список заголовков, к которым может иметь доступ JavaScript
  • allowCredentials — разрешено ли передавать учётные данные в запросах
  • allowPrivateNetwork — разрешены ли запросы из-за пределов частной сети
  • maxAge — максимальное время в секундах, которое браузер должен хранить настройки CORS

Настройка CORS в Spring Security

В Spring Security для CORS есть соответствующий конфигуратор, позволяющий сконфигурировать CORS привычным для Spring Security способом:

@Configuration
class SecurityBeans {

  @Bean
  SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http
        .cors(configurer -> {
          // Источник конфигураций CORS
          var corsConfigurationSource = new UrlBasedCorsConfigurationSource();
          // Конфигурация CORS
          var globalCorsConfiguration = new CorsConfiguration();

          // Разрешаются CORS-запросы:
          // - с сайта http://localhost:8080
          globalCorsConfiguration.addAllowedOrigin("http://localhost:8080");
          // - с нестандартными заголовками Authorization и X-CUSTOM-HEADER
          globalCorsConfiguration.addAllowedHeader(HttpHeaders.AUTHORIZATION);
          globalCorsConfiguration.addAllowedHeader("X-CUSTOM-HEADER");
          // - с передачей учётных данных
          globalCorsConfiguration.setAllowCredentials(true);
          // - с методами GET, POST, PUT, PATCH и DELETE
          globalCorsConfiguration.setAllowedMethods(List.of(
              HttpMethod.GET.name(),
              HttpMethod.POST.name(),
              HttpMethod.PUT.name(),
              HttpMethod.PATCH.name(),
              HttpMethod.DELETE.name()
          ));
          // JavaScript может обращаться к заголовку X-OTHER-CUSTOM-HEADER ответа
          globalCorsConfiguration.setExposedHeaders(List.of("X-OTHER-CUSTOM-HEADER"));
          // Браузер может кешировать настройки CORS на 10 секунд
          globalCorsConfiguration.setMaxAge(Duration.ofSeconds(10));

          // Использование конфигурации CORS для всех запросов
          corsConfigurationSource.registerCorsConfiguration("/**", globalCorsConfiguration);

          configurer.configurationSource(corsConfigurationSource);
        })
        .build();
  }
}

Если в контексте приложения зарегистрирован компоненты CorsFilter или CorsConfigurationSource, то Spring Security будет использовать их.

Оптимизация работы со Spring Boot

Spring Boot не предоставляет инструментов для быстрой настройки CORS, и вероятно не будет. Однако в реальных условиях хочется иметь возможность динамически гибко изменять настройки CORS без необходимости в последующей пересборке проекта. Для этого мне потребуется класс свойств, который будет содержать связку между шаблоном пути и параметрами CORS. Я могу также создать класс, описывающий параметры CORS, но вместо этого я буду использовать существующий CorsProperties:

@ConfigurationProperties(prefix = "spring.cors.url")
public class UrlBasedCorsConfigurationProperties {

  private Map<String, CorsConfiguration> configurations = new LinkedHashMap<>();

  public Map<String, CorsConfiguration> getConfigurations() {
    return this.configurations;
  }

  public void setConfigurations(LinkedHashMap<String, CorsConfiguration> configurations) {
    this.configurations = configurations;
  }
}

Для реализации используется именно LinkedHashMap, так как желательно применять правила в том порядке, в котором они описываются.

Теперь мне нужно зарегистрировать данный класс свойств в приложении:

@SpringBootApplication
@EnableConfigurationProperties(UrlBasedCorsConfigurationProperties.class)
public class CorsSandboxApplication {

  public static void main(String[] args) {
    SpringApplication.run(CorsSandboxApplication.class, args);
  }
}

Теперь я могу описать настройки CORS в application.yaml следующим образом:

spring:
  cors:
    url:
      configurations:
        # Настройки CORS для путей, начинающихся с /api
        "[/api/**]":
          allowed-origins:
            - http://localhost:8081
          allowed-methods:
            - GET
            - POST
            - PATCH
            - DELETE
          allowed-headers:
            - Authorization
            - Content-Type
          max-age: 10
          allow-credentials: true
        # Для всех запросов разрешаются CORS-запросы:
        "[/**]":
          # - с сайта http://localhost:8080
          allowed-origins:
            - http://localhost:8080
          # - с методами GET и POST
          allowed-methods:
            - GET
            - POST
          # - с нестандартными заголовками Authorization и Content-Type
          allowed-headers:
            - Authorization
            - Content-Type
          # - с передачей учётных данных
          allow-credentials: true
          # JavaScript может обращаться к заголовку X-CUSTOM-HEADER ответа
          exposed-headers:
            - X-CUSTOM-HEADER
          # Браузер может кешировать настройки CORS на 10 секунд
          max-age: 10

Обратите ваше внимание на экранирование шаблонов путей: "[/**]", нужно это чтобы не терялся ведущий / и не возникало ошибок парсинга.

Дело осталось за малым — использовать UrlBasedCorsConfigurationProperties для создания CorsFilter. Если не используется Spring Security, это будет выглядеть следующим образом:

@Configuration
class WebBeans {

  @Bean
  CorsFilter corsFilter(UrlBasedCorsConfigurationProperties properties) {
    // Источник конфигураций CORS
    var source = new UrlBasedCorsConfigurationSource();

    properties.getConfigurations()
      .forEach(source::registerCorsConfiguration);

    return new CorsFilter(source);
  }
}

И если используется Spring Security:

@Configuration
class SecurityBeans {

  @Bean
  SecurityFilterChain securityFilterChain(HttpSecurity http,
                                          UrlBasedCorsConfigurationProperties properties) throws Exception {
    return http
        .cors(configurer -> {
          // Источник конфигураций CORS
          var source = new UrlBasedCorsConfigurationSource();

          properties.getConfigurations()
            .forEach(source::registerCorsConfiguration);

          configurer.configurationSource(source);
        })
        .build();
  }
}

Полезные ссылки


Report Page