Spring Security 6.2.1 + Vue.js 3 (axios) : Недействительный CSRF токен в POST запросе (код 403)

Вопрос или проблема

Я создаю одностраничное приложение с Spring и Vue, но не могу заставить работать защиту от CSRF. Я следовал тому, что предлагает эта тема, но токен CSRF не обнаруживается ни с одним из имен (_csrf, X-XSRF-TOKEN, X-XSRF, X-CSRF-TOKEN) в моем POST-запросе, хотя заголовок SET-COOKIES с токеном есть в ответе.

Вот моя конфигурация безопасности:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true)
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http
                .csrf((csrf) -> csrf
                        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                        .csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler())
                )
                .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class);
        return http.build();
    }
}

final class SpaCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler {
    private final CsrfTokenRequestHandler delegate = new XorCsrfTokenRequestAttributeHandler();

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, Supplier csrfToken) {
        /*
         * Всегда используйте XorCsrfTokenRequestAttributeHandler для обеспечения защиты от BREACH
         * CsrfToken, когда он отображается в теле ответа.
         */
        this.delegate.handle(request, response, csrfToken);
    }

    @Override
    public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
        /*
         * Если запрос содержит заголовок запроса, используйте CsrfTokenRequestAttributeHandler
         * для разрешения CsrfToken. Это применяется, когда одностраничное приложение включает
         * значение заголовка автоматически, которое было получено через cookie, содержащий
         * необработанный CsrfToken.
         */
        if (StringUtils.hasText(request.getHeader(csrfToken.getHeaderName()))) {
            return super.resolveCsrfTokenValue(request, csrfToken);
        }
        /*
         * Во всех остальных случаях (например, если запрос содержит параметр запроса) используйте
         * XorCsrfTokenRequestAttributeHandler для разрешения CsrfToken. Это применяется
         * когда серверная форма включает параметр запроса _csrf как скрытый ввод.
         */
        return this.delegate.resolveCsrfTokenValue(request, csrfToken);
    }
}

final class CsrfCookieFilter extends OncePerRequestFilter {
    private static final Logger logger = LoggerFactory.getLogger(CsrfCookieFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        CsrfToken csrfToken = (CsrfToken) request.getAttribute("X-CSRF-TOKEN");
        // Отобразите значение токена в cookie, заставив отложенный токен быть загруженным
        csrfToken.getToken();

        filterChain.doFilter(request, response);
    }
}

И вот запрос, который я отправляю на сервер.

export async function validateObservationsId(producer_id: string, dataset_id: string, document_ids: string[]): Boolean[] {
    const url = `${myBaseUrl}/download/observation/${producer_id}/${dataset_id}/exists`;
    // Получить токен CSRF из cookie
    const csrfToken = document.cookie
        .split('; ')
        .find(row => row.startsWith('XSRF-TOKEN='))
        ?.split('=')[1];
    console.log("CSRF Token: ", csrfToken);
    try {
        let response = await axios.post(
            url,
            document_ids,
            {
            headers: { 'X-CSRF-TOKEN': csrfToken }
        }
        );
        return response.data;
    } catch (error) {
        console.error('Ошибка при валидации идентификаторов наблюдений', error);
        return [];
    }
}

Согласно тому, что я знаю, Axios должен сам добавлять токен в запрос, но я пробовал как с ним, так и без, и ни один вариант не работает.

Ответ или решение

Решение проблемы с токеном CSRF в Spring Security 6.2.1 и Vue.js 3 (Axios): Ошибка 403

Проблема, с которой вы столкнулись, связана с механизмом защиты от межсайтовых подделок запросов (CSRF), который не может правильно аутентифицировать POST-запросы из вашего приложения на Vue.js в Spring Boot. Вероятнее всего, это обусловлено неправильной конфигурацией или процессом работы с токеном CSRF в вашем коде. Давайте разберёмся с этим шаг за шагом.

1. Проверка конфигурации Spring Security

Ваш код конфигурации Spring Security в основном правильный, однако важно уточнить несколько моментов:

  • Получение токена CSRF: Вы используете CookieCsrfTokenRepository, который инжектирует токен в cookie. Убедитесь, что у вас установлен флаг SameSite для cookie, чтобы избежать проблем с кросс-доменными запросами.

  • Обработка запроса CSRF: Ваша конфигурация правильно использует SpaCsrfTokenRequestHandler. Однако необходимо удостовериться, что AJAX-запрос от Vue.js включает правильные заголовки.

2. Получение токена CSRF в клиенте

Вы упомянули, что ваш запрос не получает токен CSRF в заголовках. Убедитесь, что токен XSRF-TOKEN действительно помещается в cookie. Вы можете использовать следующий код для проверки:

const csrfToken = document.cookie
    .split('; ')
    .find(row => row.startsWith('XSRF-TOKEN='))
    ?.split('=')[1];

console.log("CSRF Token: ", csrfToken); // Это отлаживающий вывод, который поможет вам убедиться, что токен правильно получен.

Если токен не присутствует, вам нужно оценить, когда он устанавливается. Токен должен быть доступен перед первым AJAX-запросом.

3. Настройка Axios для автоматической передачи токена CSRF

Ваша текущая реализация обработки запроса через Axios виду неплохо, но если вы хотите, чтобы Axios автоматически добавлял заголовок, вы можете настроить его следующим образом:

import axios from 'axios';

// Устанавливаем глобальную конфигурацию для Axios
axios.defaults.withCredentials = true; // Включаем кросс-доменные запросы, если необходимо

const csrfToken = document.cookie
    .split('; ')
    .find(row => row.startsWith('XSRF-TOKEN='))
    ?.split('=')[1];

if (csrfToken) {
    // Добавляем токен CSRF ко всем запросам
    axios.defaults.headers.common['X-CSRF-TOKEN'] = csrfToken;
}

4. Пример отправки POST-запроса с Axios с токеном CSRF

Когда у вас есть токен CSRF, вы можете отправить POST-запрос следующим образом:

export async function validateObservationsId(producer_id: string, dataset_id: string, document_ids: string[]): Promise<Boolean[]> {
    const url = `${myBaseUrl}/download/observation/${producer_id}/${dataset_id}/exists`;

    try {
        const response = await axios.post(url, document_ids);
        return response.data; // Асинхронно возвращаем результат
    } catch (error) {
        console.error('Ошибка валидации идентификаторов наблюдений', error);
        return [];
    }
}

5. Дополнительные шаги по отладке

Если проблема сохраняется:

  • Проверьте консоль разработчика в браузере на наличие ошибок и убедитесь, что заголовок X-CSRF-TOKEN правильно отправляется в сетевых запросах.
  • Ознакомьтесь с журналами вашего сервера Spring для дополнительных деталей о причине ошибки 403.
  • Убедитесь, что все домены и маршруты правильно настроены в CORS в Spring Boot, если ваш фронтенд и бэкенд запущены на разных доменах.

Заключение

Следуя вышеперечисленным рекомендациям, вы должны сможете устранить проблему с недействительным токеном CSRF в вашем приложении. Надеюсь, эти шаги помогут вам успешно решить вашу проблему, и ваше приложение на Vue.js будет корректно взаимодействовать с Spring Security.

Оцените материал
Добавить комментарий

Капча загружается...