В предыдущей статье я постарался вкратце объяснить, что такое протокол CORS и как он работает. В этой статье я предлагаю разобраться с настройками CORS на стороне Spring Web, Spring Security и Spring Boot.
Настройка CORS в Spring Web
За поддержку CORS на стороне веб-приложений, основанных на Servlet API, в экосистеме Spring отвечает именно Spring Web — фильтр CorsFilter
(не путать с одноимённым фильтром из Catalina) и все необходимые для его настройки классы и интерфейсы находятся именно в этом модуле, а не в Spring Security. Поэтому для включения поддержки CORS в вашем веб-приложении на основе Spring вам не потребуются дополнительные зависимости.
Всё, что нужно для настройки CORS — зарегистрировать в контексте приложения компонент CorsFilter
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
@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
— список заголовков, к которым может иметь доступ JavaScriptallowCredentials
— разрешено ли передавать учётные данные в запросахallowPrivateNetwork
— разрешены ли запросы из-за пределов частной сетиmaxAge
— максимальное время в секундах, которое браузер должен хранить настройки CORS
Настройка CORS в Spring Security
В Spring Security для CORS есть соответствующий конфигуратор, позволяющий сконфигурировать CORS привычным для Spring Security способом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
@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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@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
, так как желательно применять правила в том порядке, в котором они описываются.
Теперь мне нужно зарегистрировать данный класс свойств в приложении:
1 2 3 4 5 6 7 8 |
@SpringBootApplication @EnableConfigurationProperties(UrlBasedCorsConfigurationProperties.class) public class CorsSandboxApplication { public static void main(String[] args) { SpringApplication.run(CorsSandboxApplication.class, args); } } |
Теперь я могу описать настройки CORS в application.yaml следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
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, это будет выглядеть следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Configuration class WebBeans { @Bean CorsFilter corsFilter(UrlBasedCorsConfigurationProperties properties) { // Источник конфигураций CORS var source = new UrlBasedCorsConfigurationSource(); properties.getConfigurations().forEach(source::registerCorsConfiguration); return new CorsFilter(source); } } |
И если используется Spring Security:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@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(); } } |