Что означает 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 нужно отключить базу данных» — грубейшее непонимание терминологии.