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 дозволяє виконувати запити між різними джерелами до анотованих методів контролера, як показано в наступному прикладі:


@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-конфігурація

Щоб активувати 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.

Щоб конфігурувати фільтр, передай 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)