Що означає Stateless в контексті REST
Воно означає, що сервер не зберігає контекст сесії клієнта між послідовними HTTP-запитами. Кожен вхідний запит зобов'язаний містити абсолютно всю інформацію, необхідну серверу д...
🟢 Junior Level
Stateless (відсутність стану) — це одне з фундаментальних архітектурних обмежень REST, сформульованих Роєм Філдінгом.
Воно означає, що сервер не зберігає контекст сесії клієнта між послідовними HTTP-запитами. Кожен вхідний запит зобов’язаний містити абсолютно всю інформацію, необхідну серверу для його розуміння, автентифікації та обробки.
[Клієнт] ─── Запит 1: "Дай профіль" (Токен: ABC) ───► [Сервер 1]
[Клієнт] ─── Запит 2: "Дай замовлення" (Токен: ABC) ──► [Сервер 2] ◄── Запит успішно оброблений іншим сервером!
Аналогія з реального життя
Уявіть віконце паспортного контролю в аеропорту:
- Офіцер прикордонної служби не зобов’язаний пам’ятати вас в обличчя або вести блокнот із записами про те, що ви підходили до нього 2 хвилини тому.
- Щоразу, підходячи до віконця, ви пред’являєте паспорт і посадковий талон (усю необхідну інформацію).
- Завдяки цьому вас може перевірити абсолютно будь-який вільний офіцер у будь-якій кабінці.
Головні переваги Stateless-підходу
- Простота горизонтального масштабування: Балансувальник навантаження (Nginx, AWS ALB, Kubernetes Service) може направити будь-який наступний запит клієнта на будь-який вільний інстанс додатка.
- Зносостійкість та відмовостійкість: Якщо один інстанс сервера аварійно завершить роботу, сесії клієнтів не «згорять». Наступний запит клієнта буде безшовно оброблений сусіднім інстансом.
- Економія оперативної пам’яті: Сервер не тримає в купі (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)
└─────────────────────────┘
Еталонне рішення:
- Використовувати ультракороткоживучі Access Tokens (термін життя 5–10 хвилин). Вони валідуються суворо локально через публічний ключ без звернення до мережі.
- Використовувати довгоживучі 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 каверзних питання з відповідями
-
Чи означає Stateless, що на сервері взагалі немає бази даних? Відповідь: Ні. Stateless забороняє зберігати на сервері стан сесії клієнта (Session State). Дані предметної області (Resource State: користувачі, рахунки, транзакції) зберігаються у постійних базах даних (PostgreSQL, MongoDB).
-
Як налаштувати Spring Security на повністю Stateless-режим і що при цьому відбувається з
HttpSession? Відповідь: Через налаштуванняSessionCreationPolicy.STATELESSуSecurityFilterChain. У цьому режимі Spring Security ніколи не створюєHttpSessionі не використовує його для отриманняSecurityContext. Контекст автентифікації будується заново для кожного запиту (зазвичай через JWT-фільтр). -
У чому полягає архітектурний парадокс відкликання JWT-токенів у Stateless REST? Відповідь: Самодостатній JWT перевіряється без звернення до БД. Але вимога миттєвого відкликання скомпрометованого токена змушує сервер перевіряти кожен запит за чорним списком (Redis Blacklist), що повертає централізовану точку відмови. Рішення: ультракороткоживучі Access-токени (5 хвилин) + ротація Refresh-токенів.
-
Що таке Sticky Sessions і чому це антипатерн для REST API? Відповідь: Sticky Sessions — механізм балансувальника навантаження, що прив’язує запити конкретного клієнта до одного й того самого фізичного сервера (за Cookie
JSESSIONID). Для REST це антипатерн, оскільки він порушує рівномірний розподіл навантаження, ускладнює автоскейлінг подів у Kubernetes і призводить до втрати сесій користувачів при падінні або перезапуску ноди.
Червоні прапорці (чого категорично не можна говорити)
- ❌ «Stateless означає, що бекенд не використовує базу даних і нічого не пише на диск» — плутанина між сесією клієнта та станом ресурсів у БД.
- ❌ «У REST сесії користувачів традиційно зберігаються в
HttpSessionна сервері» — зберігання стану сесії на сервері прямо порушує обмеження REST. - ❌ «Sticky Sessions — це рекомендований спосіб балансування для сучасних REST API» — це застарілий милиця для Stateful-монолітів.
- ❌ «Для забезпечення Stateless потрібно відключити базу даних» — грубе нерозуміння термінології.