🌐 Раздел 6 · Вопрос #2

Что означает Stateless в контексте REST

Оно означает, что сервер не хранит контекст сессии клиента между последовательными HTTP-запросами. Каждый входящий запрос обязан содержать абсолютно всю информацию, необходимую...


🟢 Junior Level

Stateless (отсутствие состояния) — это одно из фундаментальных архитектурных ограничений REST, сформулированных Роем Филдингом.

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

[Клиент] ─── Запрос 1: "Дай профиль" (Токен: ABC) ───► [Сервер 1]
[Клиент] ─── Запрос 2: "Дай заказы"   (Токен: ABC) ───► [Сервер 2] ◄── Запрос успешно обработан другим сервером!

Аналогия из реальной жизни

Представьте окошко паспортного контроля в аэропорту:

  • Офицер пограничной службы не обязан помнить вас в лицо или вести блокнот с записями о том, что вы подходили к нему 2 минуты назад.
  • Каждый раз, подходя к окошку, вы предъявляете паспорт и посадочный талон (всю необходимую информацию).
  • Благодаря этому вас может проверить абсолютно любой свободный офицер в любой кабинке.

Главные преимущества Stateless-подхода

  1. Простота горизонтального масштабирования: Балансировщик нагрузки (Nginx, AWS ALB, Kubernetes Service) может направить любой последующий запрос клиента на любой свободный инстанс приложения.
  2. Отказоустойчивость: Если один инстанс сервера аварийно завершит работу, сессии клиентов не «сгорят». Следующий запрос клиента будет бесшовно обработан соседним инстансом.
  3. Экономия оперативной памяти: Сервер не держит в куче (heap) миллионы объектов сессий неактивных пользователей.

🟡 Middle Level

Разделение состояний: Session State vs Resource State

Критически важно не путать два типа состояния в распределенных системах:

Тип состояния Где хранится Разрешено ли в REST? Примеры
Session State (Состояние сессии) Строго на клиенте ❌ Запрещено хранить на сервере Корзина покупок до оформления, выбранная вкладка UI, токен авторизации, страница пагинации.
Resource State (Состояние ресурсов) На сервере (в СУБД) ✅ Разрешено Записи о пользователях в БД, подтвержденные заказы, остатки товаров на складе.

Конфигурация Stateless в Spring Boot 3 / Spring Security 6

В экосистеме Spring Boot переход на Stateless-архитектуру требует отключения механизма HttpSession:

package com.example.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class StatelessSecurityConfig {

    private final JwtAuthenticationFilter jwtAuthFilter;

    public StatelessSecurityConfig(JwtAuthenticationFilter jwtAuthFilter) {
        this.jwtAuthFilter = jwtAuthFilter;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
            .csrf(csrf -> csrf.disable()) // CSRF не нужен в чистом Stateless API с токенами
            .sessionManagement(session -> 
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // Запрет создания HttpSession!
            )
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/v1/auth/**").permitAll()
                .anyRequest().authenticated()
            )
            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
            .build();
    }
}
package com.example.config;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Collections;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response, 
                                    FilterChain filterChain) throws ServletException, IOException {
        String authHeader = request.getHeader("Authorization");
        
        // В Stateless REST токен передается в каждом запросе в заголовке Authorization
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String jwt = authHeader.substring(7);
            if (validateToken(jwt)) {
                UsernamePasswordAuthenticationToken auth = 
                        new UsernamePasswordAuthenticationToken("user", null, Collections.emptyList());
                SecurityContextHolder.getContext().setAuthentication(auth);
            }
        }
        
        filterChain.doFilter(request, response);
    }

    private boolean validateToken(String token) {
        return token != null && !token.isBlank();
    }
}

🔴 Senior Level

Архитектурный парадокс Stateless и отзыва токенов (JWT Revocation)

Хотя JWT (JSON Web Token) позиционируется как идеальный инструмент для Stateless REST, в реальных enterprise-системах возникает жесткое требование информационной безопасности: мгновенный отзыв доступа (Revocation) при компрометации токена, смене пароля или увольнении сотрудника.

В чём состоит парадокс:

  • Классический JWT самодостаточен: он проверяется криптографической подписью без обращения в базу данных.
  • Но чтобы отозвать токен до истечения его срока жизни, сервер вынужден при каждом входящем запросе проверять наличие идентификатора токена (jti) в черном списке (Redis Token Blacklist).
  • В этот момент система де-факто вновь становится зависимой от централизованного хранилища состояний сессий!
                  ┌─────────────────────────┐
                  │  Stateless REST Service │
                  └────────────┬────────────┘
                               │ Запрос на каждый HTTP-вызов:
                               ▼
                  ┌─────────────────────────┐
                  │   Redis Token Blacklist │ ◄── Потенциальное узкое горлышко (SPOF)
                  └─────────────────────────┘

Эталонное решение:

  1. Использовать ультракороткоживущие Access Tokens (время жизни 5–10 минут). Они валидируются строго локально через публичный ключ без обращения в сеть.
  2. Использовать длинноживущие Refresh Tokens (время жизни недели/месяцы). Обращение к базе данных или Redis происходит только в момент вызова /auth/refresh раз в 10 минут, снижая нагрузку на хранилище на 99%.

Оптимистичные блокировки без сессий: ETag и If-Match

В отсутствие серверной сессии возникает риск коллизий: два пользователя одновременно открыли профиль и отправляют обновления.

Для предотвращения перезаписи данных (Lost Updates) в Stateless REST используется протокольный механизм HTTP Conditional Requests:

// 1. Клиент запрашивает ресурс
GET /api/v1/orders/100
<-- 200 OK
<-- ETag: "v1-a8b3c9"
<-- {"status": "PENDING", "amount": 500}

// 2. Клиент отправляет обновление с условием: применить только если ETag совпадает!
PUT /api/v1/orders/100
If-Match: "v1-a8b3c9"
--> {"status": "CANCELLED", "amount": 500}

// Если другой поток успел изменить ресурс раньше:
<-- 412 Precondition Failed

🎯 Шпаргалка для интервью

30-секундный ответ

Stateless в REST означает, что сервер не хранит контекст сессии клиента между последовательными HTTP-запросами. Каждый запрос должен быть полностью самодостаточным и содержать все необходимые данные для аутентификации (например, JWT Bearer токен в заголовке Authorization).

  • Resource State (данные предметной области) хранится на сервере в базе данных.
  • Session State (состояние диалога, шаг визарда, корзина) хранится строго на стороне клиента.
  • Это позволяет линейно масштабировать систему (любой инстанс сервиса за балансировщиком может обработать любой запрос) и гарантирует отказоустойчивость при падении серверов.

4 каверзных вопроса с ответами

  1. Означает ли Stateless, что на сервере вообще нет базы данных? Ответ: Нет. Stateless запрещает хранить на сервере состояние сессии клиента (Session State). Данные предметной области (Resource State: пользователи, счета, транзакции) сохраняются в постоянных базах данных (PostgreSQL, MongoDB).

  2. Как настроить Spring Security на полностью Stateless-режим и что при этом происходит с HttpSession? Ответ: Через настройку SessionCreationPolicy.STATELESS в SecurityFilterChain. В этом режиме Spring Security никогда не создает HttpSession и не использует его для получения SecurityContext. Контекст аутентификации строится заново для каждого запроса (обычно через JWT-фильтр).

  3. В чём заключается архитектурный парадокс отзыва JWT-токенов в Stateless REST? Ответ: Самодостаточный JWT проверяется без обращения к БД. Но требование мгновенного отзыва скомпрометированного токена вынуждает сервер проверять каждый запрос по черному списку (Redis Blacklist), что возвращает централизованную точку отказа. Решение: ультракороткоживущие Access-токены (5 минут) + ротация Refresh-токенов.

  4. Что такое Sticky Sessions и почему это антипаттерн для REST API? Ответ: Sticky Sessions — механизм балансировщика нагрузки, привязывающий запросы конкретного клиента к одному и тому же физическому серверу (по Cookie JSESSIONID). Для REST это антипаттерн, так как он нарушает равномерное распределение нагрузки, затрудняет автоскейлинг подов в Kubernetes и приводит к потере сессий пользователей при падении или перезапуске ноды.

Красные флаги (чего категорически нельзя говорить)

  • ❌ «Stateless означает, что бэкенд не использует базу данных и ничего не пишет на диск» — путаница между сессией клиента и состоянием ресурсов в БД.
  • ❌ «В REST сессии пользователей традиционно хранятся в HttpSession на сервере» — хранение состояния сессии на сервере прямо нарушает ограничения REST.
  • ❌ «Sticky Sessions — это рекомендуемый способ балансировки для современных REST API» — это устаревший костыль для Stateful-монолитов.
  • ❌ «Для обеспечения Stateless нужно отключить базу данных» — грубейшее непонимание терминологии.

Связанные темы