Spring MVC позволяет обрабатывать CORS (совместное использование ресурсов между разными источниками).

Из соображений безопасности браузеры запрещают AJAX-обращения к ресурсам за пределами текущего происхождения. Например, у вас может быть свой банковский счет на одной вкладке, а evil.com - на другой. Скрипты с сайта evil.com не должны иметь возможности осуществлять AJAX-запросы к API вашего банка с вашими учетными данными - например, снимать деньги с вашего счета!

Совместное использование ресурсов между разными источниками (CORS) - это спецификация W3C, реализованная в большинстве браузеров, которая позволяет определять, какие междоменные запросы разрешены, вместо того, чтобы использовать менее безопасные и менее мощные обходные пути, основанные на IFRAME или JSONP.

Обработка

Спецификация CORS различает предварительные, простые и фактические запросы. Чтобы понять, как работает CORS, можете ознакомиться с этой статьей, помимо прочего, или обратиться за подробной информацией к спецификации.

Реализации HandlerMapping из Spring WebFlux обеспечивают встроенную поддержку CORS. После успешного отображения запроса на обработчик, HandlerMapping проверяет конфигурацию CORS для данного запроса и обработчика и предпринимает дальнейшие действия. Предварительные запросы обрабатываются напрямую, а простые и фактические CORS-запросы перехватываются, валидируются, и для них устанавливаются необходимые заголовки CORS-ответа.

Для того чтобы разрешить запросы между разными источниками (то есть заголовок Origin присутствует и отличается от хоста запроса), вам необходимо обеспечить некоторую явно объявленную конфигурацию CORS. Если подходящей конфигурации CORS не найдена, то предварительные запросы отклоняются. В ответы на простые и фактические CORS-запросы не добавляются CORS-заголовки, и, следовательно, браузеры их отклоняют.

Каждый HandlerMapping может быть сконфигурирован индивидуально с помощью отображений CorsConfiguration на основе URL-шаблонов. В большинстве случаев приложения используют Java-конфигурацию WebFlux для объявления таких отображений, что приводит к созданию единой глобальной Map, передаваемой всем реализациям HandlerMapping.

Можно сочетать глобальную конфигурацию CORS на уровне HandlerMapping с более тонкой настройкой CORS на уровне обработчиков. Например, аннотированные контроллеры могут использовать аннотации @CrossOrigin на уровне классов или методов (другие обработчики могут реализовать CorsConfigurationSource).

Правила комбинирования глобальной и локальной конфигурации обычно аддитивны - например, все глобальный и все локальные источники. В случае тех атрибутов, где может быть принято только одно значение, например, allowCredentials и maxAge, локальное значение переопределяет глобальное. Более подробную информацию см. в разделе, посвященном CorsConfiguration#combine(CorsConfiguration).

Чтобы узнать больше из исходного источника или выполнить расширенную настройку, см.:

  • CorsConfiguration

  • CorsProcessor и DefaultCorsProcessor

  • AbstractHandlerMapping

@CrossOrigin

Аннотация @CrossOrigin позволяет выполнять запросы между разными источниками к аннотированным методам контроллера, как показано в следующем примере:

Java
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin
@GetMapping("/{id}")
public Mono<Account> retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public Mono<Void> remove(@PathVariable Long id) {
// ...
}
}
Kotlin
@RestController
@RequestMapping("/account")
class AccountController {
@CrossOrigin
@GetMapping("/{id}")
suspend fun retrieve(@PathVariable id: Long): Account {
// ...
}
@DeleteMapping("/{id}")
suspend fun remove(@PathVariable id: Long) {
// ...
}
}

По умолчанию @CrossOrigin разрешает:

  • Все источники.

  • Все заголовки.

  • Все HTTP-методы, с которыми сопоставлен метод контроллера.

allowCredentials не активирован по умолчанию, так как это устанавливает уровень доверия, при котором раскрывается конфиденциальная информация о пользователе (такая как файлы cookie и CSRF-токены), поэтому его следует использовать только в тех случаях, когда это действительно необходимо. Если он активирован, то либо allowOrigins должен быть установлен для одного или нескольких определенных доменов (но не специальное значение "*"), либо, как вариант, можно использовать свойство allowOriginPatterns для сопоставления с динамическим набором источников.

maxAge is set to 30 minutes.

@CrossOrigin поддерживается и на уровне класса и наследуется всеми методами. В следующем примере указан определенный домен и задано значение maxAge равное часу:

Java
@CrossOrigin(origins = "https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@GetMapping("/{id}")
public Mono<Account> retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public Mono<Void> remove(@PathVariable Long id) {
// ...
}
}
Kotlin
@CrossOrigin("https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
class AccountController {
@GetMapping("/{id}")
suspend fun retrieve(@PathVariable id: Long): Account {
// ...
}
@DeleteMapping("/{id}")
suspend fun remove(@PathVariable id: Long) {
// ...
}
}

Вы можете использовать аннотацию @CrossOrigin как на уровне класса, так и на уровне метода, как это показано в следующем примере:

Java
@CrossOrigin(maxAge = 3600) 
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin("https://domain2.com") 
@GetMapping("/{id}")
public Mono<Account> retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public Mono<Void> remove(@PathVariable Long id) {
// ...
}
}
  1. Использование аннотации @CrossOrigin на уровне класса.
  2. Использование аннотации @CrossOrigin на уровне метода.
Kotlin
@CrossOrigin(maxAge = 3600) 
@RestController
@RequestMapping("/account")
class AccountController {
@CrossOrigin("https://domain2.com") 
@GetMapping("/{id}")
suspend fun retrieve(@PathVariable id: Long): Account {
// ...
}
@DeleteMapping("/{id}")
suspend fun remove(@PathVariable id: Long) {
// ...
}
}
  1. Использование аннотации @CrossOrigin на уровне класса.
  2. Использование аннотации @CrossOrigin на уровне метода.

Глобальная конфигурация

В дополнение к тонкой настройке на уровне методов контроллера, вам, вероятно, понадобится определить и глобальную конфигурацию CORS. Вы можете настроить отображения CorsConfiguration на основе URL-адресов индивидуально для любого HandlerMapping. Однако большинство приложений используют для этого Java-конфигурацию WebFlux.

По умолчанию глобальная конфигурация включает следующее:

  • Все источники.

  • Все заголовки.

  • Методы GET, HEAD и POST.

allowCredentials не активирован по умолчанию, так как это устанавливает уровень доверия, при котором раскрывается конфиденциальная информация о пользователе (такая как файлы cookie и CSRF-токены), поэтому его следует использовать только в тех случаях, когда это действительно необходимо. Если он активирован, то либо allowOrigins должен быть установлен для одного или нескольких определенных доменов (но не специальное значение "*"), либо, как вариант, можно использовать свойство allowOriginPatterns для сопоставления с динамическим набором источников.

maxAge is set to 30 minutes.

Чтобы активировать CORS в Java-конфигурации WebFlux, можно использовать обратный вызов CorsRegistry, как показано в следующем примере:

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
    .allowedOrigins("https://domain2.com")
    .allowedMethods("PUT", "DELETE")
    .allowedHeaders("header1", "header2", "header3")
    .exposedHeaders("header1", "header2")
    .allowCredentials(true).maxAge(3600);
// Добавляем еще отображений...
}
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/api/**")
        .allowedOrigins("https://domain2.com")
        .allowedMethods("PUT", "DELETE")
        .allowedHeaders("header1", "header2", "header3")
        .exposedHeaders("header1", "header2")
        .allowCredentials(true).maxAge(3600)
// Добавляем еще отображений...
}
}

WebFilter для CORS

Вы можете применить средства поддержки CORS через встроенный CorsWebFilter, который отлично подходит для функциональных конечных точек.

Если вы будете пытаться использовать CorsFilter со Spring Security, имейте в виду, что Spring Security предусматривает встроенную поддержку CORS.

Для настройки фильтра можно объявить бин CorsWebFilter и передать CorsConfigurationSource в его конструктор, как показано в следующем примере:

Java
@Bean
CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// Возможно...
// config.applyPermitDefaultValues()
config.setAllowCredentials(true);
config.addAllowedOrigin("https://domain1.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
Kotlin
@Bean
fun corsFilter(): CorsWebFilter {
val config = CorsConfiguration()
// Возможно...
// config.applyPermitDefaultValues()
config.allowCredentials = true
config.addAllowedOrigin("https://domain1.com")
config.addAllowedHeader("*")
config.addAllowedMethod("*")
val source = UrlBasedCorsConfigurationSource().apply {
registerCorsConfiguration("/**", config)
}
return CorsWebFilter(source)
}