🌐 Розділ 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 потрібно відключити базу даних» — грубе нерозуміння термінології.

Пов’язані теми