sequenceDiagram
autonumber
actor App as APP (Мобильное приложение)
participant Nginx as Nginx Proxy
participant GW as GATEWAY (backend-api)
participant Auth as AUTH (auth-service)
participant Redis as REDIS (Token Blacklist)
participant DB as DB_AUTH (PostgreSQL)
App->>Nginx: Шаг 1: POST /api/v1/auth/refresh (refresh_token)
activate Nginx
Nginx->>GW: Шаг 2: POST /backend-api/v1/auth/refresh
activate GW
GW->>Auth: Шаг 3: gRPC: VerifyAndRotate(RefreshRequest)
activate Auth
Auth->>Redis: Шаг 4: Redis.EXISTS(revoked_refresh)
activate Redis
note over Auth, Redis: При падении Redis:<br/>Fail-Close -> Аварийный выход HTTP 503
Redis-->>Auth: Шаг 5: Токен НЕ найден в черном списке
deactivate Redis
Auth->>DB: Шаг 6: SQL SELECT is_revoked FROM user_sessions...
activate DB
DB-->>Auth: Токен валиден, срок не истек
deactivate DB
note over Auth: Шаг 7: Внутреннее действие:<br/>Атомарная деактивация старого<br/>и генерация нового токена
Auth->>DB: Шаг 7 (продолжение): SQL UPDATE и INSERT сессий
activate DB
DB-->>Auth: Сессия обновлена
deactivate DB
Auth-->>GW: Шаг 8: gRPC: RotateResponse
deactivate Auth
GW-->>App: Шаг 9: HTTP 200 OK (new access, new refresh)
deactivate GW
deactivate Nginx
note over App: Шаг 10: Внутренний метод<br/>SecureStorage.write(...) и разморозка очереди
Метод POST /api/v1/auth/refresh
Документация API: Фоновое обновление сессии (Refresh Token) с бесшовным перевыпуском Multi-Tenancy JWT Claims
- IAD-MIGRATION-116 Backlog — Краткое Описание задачи 1.
- IAD-MIGRATION-117 Refinement (Уточнение) — Краткое Описание задачи 2.
- GW-2 Ready for Development — Подключение эндпоинта в шлюзе.
- AID-012 Ready for Development — создать метож ProcessVoiceStream.
- AID-013 Ready for Development — создать метож ProcessUploadText.
- INV-FRONTEND-222 Refinement (Уточнение) — Экран управления B2B-интеграциями
- INV-FRONTEND-223 Refinement (Уточнение) — Экран управления B2B-интеграциями
1 Функциональное назначение
Метод предназначен для безопасного продолжения активной пользовательской сессии без повторного принудительного ввода логина и пароля на смартфоне.
В рамках микросервисной архитектуры этот эндпоинт решает критические инфраструктурные задачи:
- Обслуживание Dio Interceptor: Метод вызывается сетевым слоем Flutter-приложения строго в фоновом режиме, когда шлюз возвращает ошибку
HTTP 401 Unauthorizedна любой транзакционный запрос. - Бесшовный перевыпуск контекста: Проверяет долгоживущий
Refresh Tokenпо базе сессий и генерирует новый короткоживущийAccess Token(JWT), сохраняя актуальные JWT Private Claimshome_group_id,account_typeиuser_id. Это гарантирует, что фоновые процессы сокетов и транзакции не прервутся для пользователя. - Механизм обновления токенов (Refresh Token Rotation): Для защиты от компрометации, при каждом вызове старый Refresh-токен аннулируется, а клиенту выдается новая пара токенов, что предотвращает атаки типа Replay Attack.
2 Протокол взаимодействия (HTTP Контракт)
- Метод:
POST - Маршрут:
/api/v1/auth/refresh - Формат данных:
application/json
2.1 Спецификация заголовков (HTTP Headers)
| Заголовок | Обязательный | Описание | Пример значения |
|---|---|---|---|
Content-Type |
Да | Указывает на передачу строго типизированного JSON-пакета | application/json |
X-Request-ID |
Да | Сквозной ID запроса для трассировки фоновой ротации сессии | req-auth-ref-22aa |
2.2 Спецификация тела запроса (Request Body)
В соответствии с требованиями безопасности, Refresh-токен передается в теле JSON-запроса (или извлекается из защищенных HttpOnly Cookies в зависимости от конфигурации окружения).
| Поле | Тип | Обязательный | Описание | Пример значения |
|---|---|---|---|---|
refresh_token |
String | Да | Долгоживущий токен обновления, выданный при входе в систему | ref-token-xyz789... |
2.2.1 Пример сырого JSON-запроса (Payload):
{
"refresh_token": "ref-token-xyz789_v1_signature..."
}2.3 Спецификация успешного ответа (Response Body)
2.3.1 HTTP 200 OK
Возвращается при успешной валидации сессии. Выдается обновленный JWT-токен.
{
"status": "success",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3UrODgyMiIsImhvbWVfZ3JvdXBfaWQiOiJncm91cC03NzItYWxwaGEiLCJhY2NvdW50X3R5cCI6IkhPTUUiLCJleHAiOjE3ODMzMTMyMDB9.new_signature...",
"expires_in": 3600
}3 Схема обработки запроса пользователя (Mermaid)
На диаграмме представлена логика работы Dio Interceptor при фоновом обновлении токенов. Сервис Auth проверяет refresh_token по базе активных сессий, выполняет обновление, аннулирует старый ключ и выпускает новый JWT с сохранением JWT Private Claims.
4 Расшифровка шагов и логика ротации токенов
| Шаг | Действие | Параметры | Ошибки (Исключения / Статусы) |
|---|---|---|---|
Шаг 1 (APP -> NGINX) |
Перехватчик приложения (Dio Interceptor), поймав HTTP 401, замораживает очередь, достает из хранилища refresh_token и отправляет запрос ротации. |
HTTP POST /api/v1/auth/refreshBody: {"refresh_token": "..."} |
DioException: connection timeout |
Шаг 2 (NGINX -> GATEWAY) |
Nginx пересылает запрос на внутренний API Gateway. |
HTTP POST /backend-api/v1/auth/refresh |
HTTP 502 Bad Gateway |
Шаг 3 (GATEWAY -> AUTH) |
API Gateway проверяет контракт и вызывает внутренний gRPC-метод ротации сессии. | gRPC: VerifyAndRotate(RefreshRequest) |
HTTP 422 Unprocessable Entity |
Шаг 4 (AUTH -> REDIS) |
auth-service проверяет, не находится ли входящий Refresh Token в черном списке отозванных сессий. |
Вызов: Redis.EXISTS(f"revoked_refresh:{jti}") |
Redis: ConnectionRefusedErrorgRPC: UNAVAILABLE \(\rightarrow\) Fail-Close! HTTP 503 |
Шаг 5 (REDIS -> AUTH) |
Redis возвращает статус проверки. Если токен в черном списке — сессия считается скомпрометированной. | Результат проверки в Redis | gRPC: UNAUTHENTICATEDHTTP 401 / IAD_JWT_REFRESH_STOLEN |
Шаг 6 (AUTH -> DB_AUTH) |
Сервис проверяет валидность сессии и ее срок годности (expires_at) в транзакционной базе данных. |
SQL SELECT is_revoked FROM user_sessions WHERE refresh_token_jti = $1 |
gRPC: UNAUTHENTICATEDHTTP 401 / IAD_JWT_REFRESH_EXPIRED |
Шаг 7 (AUTH -> DB_AUTH) |
Внутреннее действие: Сервис атомарно деактивирует старый Refresh Token и записывает данные новой сессии для защиты от повторного использования токена. |
SQL UPDATE user_sessions ...SQL INSERT INTO user_sessions ... |
PostgreSQL: transaction_deadlockgRPC: ABORTED \(\rightarrow\) HTTP 410 Gone |
Шаг 8 (AUTH -> GATEWAY) |
Сервис возвращает новую сгенерированную пару токенов на API Gateway по gRPC. | gRPC: RotateResponse |
gRPC: INTERNAL |
Шаг 9 (GATEWAY -> APP) |
API Gateway передает обновленные данные через Nginx обратно в мобильное приложение. | HTTP 200 OKBody: {"access_token": "...", "refresh_token": "..."} |
HTTP 500 Internal Server Error |
Шаг 10 (APP -> APP) |
Приложение обновляет ключи в secure storage, размораживает очередь и прозрачно перезапускает упавший пользовательский запрос. | Вызов: SecureStorage.write(...) |
SecureStorageException |
5 Спецификация ответов сервера (Response Body) и ошибок
5.1 1. Спецификация структуры сессий PostgreSQL (Token Rotation Layer)
Для поддержки безопасного механизма обновления токенов и определения повторного использования угнанных ключей (Reuse Detection), база данных хранит метаданные сессий в изолированной таблице.
-- Таблица сессий и долгоживущих Refresh-токенов
CREATE TABLE user_sessions (
id BIGSERIAL PRIMARY KEY,
user_id VARCHAR(50) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token VARCHAR(512) NOT NULL UNIQUE, -- Хэш или тело Refresh-токена
is_revoked BOOLEAN NOT NULL DEFAULT FALSE,-- Флаг аннулирования токена (использован/украден)
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Индекс для мгновенной верификации токена при фоновом обновлении Dio Interceptor
CREATE INDEX idx_user_sessions_lookup ON user_sessions(token) WHERE is_revoked = FALSE;5.2 2. Вилки исключений и обработка ошибок
5.2.2 Ошибка: Токен поврежден или имеет неверный формат (HTTP 422 Unprocessable Entity)
Выбрасывается бэкенд-шлюзом FastAPI, если переданная строка в поле refresh_token пустая или нарушает валидацию длины Pydantic-модели.
{
"error_code": "ERR-VALIDATION-FAILED",
"message": "Передан некорректный или пустой токен обновления refresh_token.",
"details": [
{
"loc": ["body", "refresh_token"],
"msg": "Field constraints violation: token string cannot be empty",
"type": "value_error.str.min_length"
}
]
}