P2-003 JWT токены и refresh механизм¶
Метаданные¶
| Поле | Значение |
|---|---|
| Фаза | Phase 2: Core |
| Статус | done |
| Приоритет | critical |
| Связь с roadmap | Roadmap - Аутентификация |
Контекст¶
Бизнес-контекст¶
JWT токены обеспечивают stateless аутентификацию между клиентом и сервисами. Access token имеет короткий срок жизни (15 минут) для безопасности, refresh token — длинный (7 дней) для удобства пользователя.
Технический контекст¶
JwtTokenProviderреализован вcommon-security- Генерация access и refresh токенов работает
- Хранение refresh токенов в БД с механизмом ротации реализовано
- Gateway валидирует access токены через
JwtAuthenticationFilter
Связанные документы: - Security — требования к токенам - User Service — структура токенов - Common Library — JwtTokenProvider
Реализованный код: - JwtTokenProvider.java - TokenHasher.java - RefreshToken.java - AuthService.java - AuthController.java
Цель¶
Реализовать полный механизм JWT токенов с хранением refresh токенов в БД, ротацией и отзывом.
Definition of Ready (DoR)¶
- [x] Контекст понятен и описан
- [x] Цель сформулирована
- [x] Acceptance Criteria определены
- [x] Технические детали проработаны
- [x] Зависимости определены и разрешены
- [x] Нет блокеров
Acceptance Criteria¶
- [x] Access token содержит: userId, email, tenantId, roles
- [x] Access token действителен 15 минут (FR-1.2.4)
- [x] Refresh token действителен 7 дней (FR-1.2.5)
- [x] Refresh token хранится в БД (hashed) —
TokenHasher.hash()SHA-256 - [x] При использовании refresh token — выдаётся новая пара токенов (rotation)
- [x] Использованный refresh token помечается как revoked
- [x] Endpoint
POST /api/v1/auth/refreshработает корректно - [x] Endpoint
POST /api/v1/auth/logoutотзывает все refresh токены пользователя - [x] При выходе все активные сессии завершаются
- [x] Refresh token одноразовый (one-time use)
Definition of Done (DoD)¶
- [x] Все Acceptance Criteria выполнены
- [x] Код написан согласно code style проекта
- [x] Unit тесты для AuthService написаны (включая refresh, logout, revokeToken)
- [x] Integration тесты написаны (AuthControllerIntegrationTest)
- [x] Миграции для таблицы
refresh_tokensсозданы - [x] CI/CD pipeline проходит
Технические детали¶
Затрагиваемые компоненты¶
- [x] Backend:
user-service-db(RefreshToken entity),user-service-service(AuthService) - [ ] Frontend: interceptor для автоматического refresh (P2-015)
- [x] Database: таблица
refresh_tokens - [x] Infrastructure: TokenCleanupService для очистки истёкших токенов
Модель данных RefreshToken¶
CREATE TABLE user_service.refresh_tokens (
id UUID PRIMARY KEY,
user_id UUID NOT NULL REFERENCES user_service.users(id),
token_hash VARCHAR(64) NOT NULL,
expires_at TIMESTAMP NOT NULL,
revoked BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(token_hash)
);
CREATE INDEX idx_refresh_tokens_user_id ON user_service.refresh_tokens(user_id);
CREATE INDEX idx_refresh_tokens_token_hash ON user_service.refresh_tokens(token_hash) WHERE NOT revoked;
Алгоритм refresh¶
- Клиент отправляет refresh token
- Сервис ищет токен по hash в БД
- Проверяет: не revoked, не expired
- Помечает старый токен как revoked
- Генерирует новую пару токенов
- Сохраняет новый refresh token
- Возвращает клиенту
Безопасность¶
- Refresh token хранится как SHA-256 hash
- При компрометации одного токена — автоматический отзыв всех (anomaly detection)
- Cleanup job для удаления старых токенов
Зависимости¶
Блокирует¶
- Все задачи требующие аутентификации
Зависит от¶
- P2-001 User entity и базовая структура
Out of Scope¶
- Sliding session expiration
- Device tracking (user agent и IP сохраняются для аудита, но не используются для блокировки)
Реализовано дополнительно¶
- Concurrent session limits — MAX_ACTIVE_SESSIONS = 10, старые сессии автоматически отзываются
- jti (JWT ID) — уникальный ID для каждого refresh token
- TokenCleanupService — scheduled job для очистки истёкших токенов (каждый час)
- Audit metadata — сохранение user_agent и ip_address для каждого токена
Заметки¶
JwtTokenProviderиспользует HMAC (HS256) — это правильно для нашего случая- В production JWT_SECRET должен быть минимум 256 бит