← Назад к вопросам

Что такое CSRF токен?

2.0 Middle🔥 141 комментариев
#Spring Framework#Безопасность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое CSRF токен

Определение

CSRF (Cross-Site Request Forgery) токен — это уникальный, непредсказуемый и невоспроизводимый код, который предотвращает атаки типа CSRF (подделка межсайтовых запросов).

Это один из наиболее эффективных механизмов защиты веб-приложений от атак, при которых злоумышленник заставляет аутентифицированного пользователя выполнить нежелательные действия на другом сайте.

Проблема: CSRF атака

Сценарий без защиты:

  1. Пользователь залогинился на bank.com
  2. Пользователь посещает evil.com (не закрыв вкладку банка)
  3. На evil.com есть скрытый запрос:
<!-- На зло
умышленническом сайте evil.com -->
<img src="http://bank.com/api/transfer?to=attacker&amount=1000" style="display:none;" />
  1. Браузер автоматически отправляет этот запрос с cookies пользователя
  2. Банк трансферит деньги, думая, что запрос от самого пользователя

Решение: CSRF токен

CSRF токен гарантирует, что запрос пришёл со своего сайта, а не с чужого. Вот как это работает:

Шаг 1: Сервер генерирует токен

@RestController
@RequestMapping("/api")
public class CsrfController {
    
    private final CsrfTokenRepository tokenRepository;
    
    @GetMapping("/csrf-token")
    public ResponseEntity<CsrfTokenDTO> getCsrfToken(
        HttpServletRequest request,
        HttpSession session
    ) {
        // Генерируем уникальный CSRF токен
        String csrfToken = UUID.randomUUID().toString();
        
        // Сохраняем в сессии
        session.setAttribute("_csrf_token", csrfToken);
        
        return ResponseEntity.ok(
            new CsrfTokenDTO(csrfToken)
        );
    }
}

Шаг 2: Клиент отправляет форму с токеном

<!-- Форма на сайте bank.com -->
<form action="/api/transfer" method="POST">
    <!-- Скрытое поле с CSRF токеном -->
    <input type="hidden" name="_csrf" value="abc123xyz..." />
    
    <input type="text" name="to" placeholder="Получатель" />
    <input type="number" name="amount" placeholder="Сумма" />
    <button type="submit">Отправить</button>
</form>

Шаг 3: Сервер проверяет токен

@PostMapping("/transfer")
public ResponseEntity<?> transferMoney(
    @RequestParam String to,
    @RequestParam BigDecimal amount,
    @RequestParam("_csrf") String csrfToken,
    HttpSession session
) {
    // Проверяем, что токен совпадает с сохранённым в сессии
    String expectedToken = (String) session.getAttribute("_csrf_token");
    
    if (!csrfToken.equals(expectedToken)) {
        // CSRF атака!
        return ResponseEntity.status(403).body("CSRF validation failed");
    }
    
    // Выполняем операцию
    transferService.transfer(to, amount);
    return ResponseEntity.ok("Transfer successful");
}

Почему токен защищает

Злоумышленник не может угадать токен:

// На evil.com он пытается отправить запрос
// <img src="http://bank.com/api/transfer?to=attacker&amount=1000&_csrf=??? " />
// Но он НЕ знает значение токена!

Токен уникален для каждого пользователя и сессии:

public class CsrfTokenGenerator {
    
    public String generateToken() {
        // Генерируем 256-битный токен
        SecureRandom random = new SecureRandom();
        byte[] tokenBytes = new byte[32];
        random.nextBytes(tokenBytes);
        
        // Кодируем в hex для удобства
        return HexFormat.of().formatHex(tokenBytes);
    }
}

Реализация в Spring Security

Spring Security автоматически управляет CSRF токенами:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .ignoringRequestMatchers("/api/public/**")  // Исключения
            )
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .anyRequest().authenticated()
            );
        
        return http.build();
    }
}

Передача токена в Headers (для AJAX)

Получение токена:

// В HTML
<meta name="_csrf" content="{{ csrfToken }}" />
<meta name="_csrf_header" content="X-CSRF-TOKEN" />

// JavaScript
const token = document.querySelector('meta[name="_csrf"]').getAttribute('content');
const header = document.querySelector('meta[name="_csrf_header"]').getAttribute('content');

Отправка запроса:

fetch('/api/transfer', {
    method: 'POST',
    headers: {
        'X-CSRF-TOKEN': token,
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        to: 'recipient',
        amount: 1000
    })
});

Проверка на сервере:

@PostMapping("/transfer")
public ResponseEntity<?> transfer(
    @RequestBody TransferRequest request,
    @RequestHeader("X-CSRF-TOKEN") String csrfToken,
    HttpSession session
) {
    String expectedToken = (String) session.getAttribute("_csrf_token");
    
    if (!csrfToken.equals(expectedToken)) {
        return ResponseEntity.status(403).build();
    }
    
    // Выполняем операцию
    return ResponseEntity.ok(transferService.transfer(request));
}

Типы хранения CSRF токена

ТипГде хранитсяПлюсыМинусы
SessionВ сессии сервераБезопасноТребует сессию
CookieВ cookie браузераНе требует сессиюМожет быть украден XSS
Token storeВ памяти на сервереМасштабируемоСложнее

Лучшие практики

  • Генерируй токен на каждый запрос (или на сессию)
  • Никогда не передавай токен в URL (только в headers или body)
  • Используй HTTPS для передачи токена
  • Исключай безопасные методы (GET, HEAD) из проверки
  • Устанавливай краткое время жизни для токена
  • Комбинируй с другими защитами (SameSite cookies, CSP)

SameSite Cookies — современная защита

@Configuration
public class CookieConfig {
    
    @Bean
    public DefaultHttpFirewall httpFirewall() {
        DefaultHttpFirewall firewall = new DefaultHttpFirewall();
        // SameSite автоматически защищает от CSRF
        return firewall;
    }
}

Одновременно с установкой:

response.addHeader(
    "Set-Cookie",
    "SESSION=abc123; Path=/; HttpOnly; Secure; SameSite=Strict"
);

Заключение

CSRF токен — это критически важный механизм защиты веб-приложений. Он гарантирует, что все состояние-меняющие операции (POST, PUT, DELETE) выполняются только с разрешения владельца сеанса. Современные фреймворки вроде Spring Security управляют CSRF автоматически, но понимание механизма работы необходимо каждому разработчику для безопасной разработки приложений.