null

Как правильно настроить поддержку CORS в Spring API с использованием Spring Security

Введение

Иногда самые простые задачи в разработке могут превращаться в настоящую головоломку. Так случилось и с нами: наша команда столкнулась с неожиданной проблемой при интеграции API, написанного на Java с использованием Spring, и сайта-лендинга заказчика. Ошибка CORS (Cross-Origin Resource Sharing) стала барьером для нормального взаимодействия между сервисами. В этой статье я расскажу, как мы решили проблему и какие подходы лучше использовать в зависимости от конфигурации вашего проекта.

Проблема: CORS и как она возникает

CORS — это механизм, который ограничивает взаимодействие между различными доменами. Это важная функция безопасности, но она также может стать препятствием, если ваш фронтенд (лендинг) и API развернуты на разных серверах и доменах.

В нашем случае лендинг вызывал API, развернутое на другом сервере, и получил стандартное сообщение об ошибке CORS.

Решение с использованием аннотации @CrossOrigin

Первый интуитивный шаг — использование встроенной аннотации @CrossOrigin. Вот пример:

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/some-amazing-api")
@Slf4j
public class UserController {

    @CrossOrigin(origins = {"http://localhost:3000",
            "https://landing-url-1",
            "https://landing-url-2",
            "https://landing-url-3"},
            allowCredentials = "true")
    @PostMapping("/some-amazing-api-url")
    public ResponseEntity<?> amazingControllerMethod(@RequestBody @Valid AmazingRequestBody requestBody) {
        return ResponseEntity.status(HttpStatus.OK)
                .body("Hello from Tune-it!!!");
    }
}

Эта аннотация указывает допустимые домены, с которых разрешено выполнять запросы к API. Однако, если ваш проект использует Spring Security, это решение работать не будет, так как Spring Security имеет собственные ограничения и правила обработки CORS.

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

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

Подход для версии Spring Security до 5.7

@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .cors().configurationSource(corsConfigurationSource())
                .and()
                // другие настройки безопасности
                .authorizeRequests().anyRequest().authenticated();
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        var corsConfig = new CorsConfiguration();
        corsConfig.setAllowedOrigins(
                List.of("http://localhost:3000",
                        "https://landing-url-1",
                        "https://landing-url-2",
                        "https://landing-url-3"));
        corsConfig.setAllowCredentials(true);
        corsConfig.setAllowedMethods(List.of("*"));
        corsConfig.setAllowedHeaders(List.of("*"));

        var urlBasedConfig = new UrlBasedCorsConfigurationSource();
        urlBasedConfig.registerCorsConfiguration("/api/some-amazing-api/some-amazing-api-url", corsConfig);
        urlBasedConfig.registerCorsConfiguration("/api/some-amazing-api/some-amazing-api-url-2", corsConfig);
        return urlBasedConfig;
    }
}

Подход для версии Spring Security 5.7 и выше

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

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        var corsConfig = new CorsConfiguration();
        corsConfig.setAllowedOrigins(
                List.of("http://localhost:3000",
                        "https://landing-url-1",
                        "https://landing-url-2",
                        "https://landing-url-3"));
        corsConfig.setAllowCredentials(true);
        corsConfig.setAllowedMethods(List.of("*"));
        corsConfig.setAllowedHeaders(List.of("*"));

        var urlBasedConfig = new UrlBasedCorsConfigurationSource();
        urlBasedConfig.registerCorsConfiguration("/api/some-amazing-api/**", corsConfig);
        return urlBasedConfig;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .cors(cors -> cors.configurationSource(corsConfigurationSource()))
                // другие настройки
                .authorizeHttpRequests().anyRequest().authenticated();
        return http.build();
    }
}

Заключение

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

Теперь, вооружённые этими знаниями, вы сможете без труда настроить взаимодействие между фронтендом и API, даже если они работают на разных серверах и доменах.

Вперед