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

Введение

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

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

Обработка

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

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

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

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

Можно сочетать глобальную настройку 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 Account retrieve(@PathVariable Long id) {
        // ...
    }
    @DeleteMapping("/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}
Kotlin
@RestController
@RequestMapping("/account")
class AccountController {
    @CrossOrigin
    @GetMapping("/{id}")
    fun retrieve(@PathVariable id: Long): Account {
        // ...
    }
    @DeleteMapping("/{id}")
    fun remove(@PathVariable id: Long) {
        // ...
    }
}

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

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

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

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

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

maxAge устанавливается на 30 минут.

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

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

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

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

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

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

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

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

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

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

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

maxAge устанавливается на 30 минут.

Java-конфигурацияn

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

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
    @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
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
    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)
        // Добавляем больше отображений...
    }
}

XML-конфигурация

Чтобы активировать CORS в пространстве имен XML, можно использовать элемент <mvc:cors>, как показано в следующем примере:

<mvc:cors>
    <mvc:mapping path="/api/**"
        allowed-origins="https://domain1.com, https://domain2.com"
        allowed-methods="GET, PUT"
        allowed-headers="header1, header2, header3"
        exposed-headers="header1, header2" allow-credentials="true"
        max-age="123" />
    <mvc:mapping path="/resources/**"
        allowed-origins="https://domain1.com" />
</mvc:cors>

CORS-фильтр

Вы можете применить поддержку CORS через встроенный CorsFilter.

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

Чтобы сконфигурировать фильтр, передайте CorsConfigurationSource в его конструктор, как показано в следующем примере:

Java
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);
CorsFilter filter = new CorsFilter(source);
Kotlin
val config = CorsConfiguration()
// Возможно...
// config.applyPermitDefaultValues()
config.allowCredentials = true
config.addAllowedOrigin("https://domain1.com")
config.addAllowedHeader("*")
config.addAllowedMethod("*")
val source = UrlBasedCorsConfigurationSource()
source.registerCorsConfiguration("/**", config)
val filter = CorsFilter(source)