Подробный гайд Single session per user — разрешение нескольких сессий под одним аккаунтом

Архитектура множественных сессий: проектирование БД, связка JWT и Refresh-токенов, лимиты, безопасность и UI управления активными устройствами.

2026.07.20                  


Подробный гайд Single session per user — разрешение нескольких сессий под одним аккаунтомПодробный гайд Single session per user — разрешение нескольких сессий под одним аккаунтом Переход от модели «Single session per user» (один пользователь = одна активная сессия, вход с нового устройства сбрасывает старую) к модели «Multiple sessions per user» (пользователь может быть авторизован одновременно на телефоне, планшете и ПК) — это стандартное требование для современных приложений.

Ниже представлен подробный технический гайд по архитектуре, реализации и безопасности этой функции.


1. Архитектурный выбор: Где храним сессии?

Чтобы разрешить несколько сессий, вам нужно отказаться от хранения состояния в памяти приложения (in-memory) и перейти к централизованному хранилищу.

* Вариант А: Server-side sessions (Redis / БД).

Классический подход. Сервер генерирует session_id, отдает клиенту (в cookie или header), а сам хранит данные в Redis или БД.

* Вариант Б: JWT + Refresh Tokens (БД / Redis).

Если вы используете Stateless JWT, сами по себе они не позволяют отзывать конкретные сессии. Вам обязательно нужно хранить Refresh Token (или список активных JTI - JWT ID) в БД/Redis для каждой сессии.


В этом гайде мы будем опираться на гибридный подход (БД/Redis для хранения метаданных сессий), так как он наиболее универсален.


2. Изменение схемы базы данных (Data Model)

В модели «одна сессия» у вас, скорее всего, было поле session_token прямо в таблице users. Теперь нам нужна отдельная таблица user_sessions.

Пример схемы таблицы user_sessions:

CREATE TABLE user_sessions (
    id UUID PRIMARY KEY,
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    refresh_token VARCHAR(255) UNIQUE NOT NULL, -- или session_id
    device_info JSONB,                          -- { "browser": "Chrome", "os": "Windows", "type": "desktop" }
    ip_address INET,
    created_at TIMESTAMP DEFAULT NOW(),
    last_active_at TIMESTAMP DEFAULT NOW(),
    expires_at TIMESTAMP NOT NULL,
    is_revoked BOOLEAN DEFAULT FALSE
);

-- Индекс для быстрого поиска сессий пользователя
CREATE INDEX idx_user_sessions_user_id ON user_sessions(user_id);

#### 3. Логика работы (Backend)

А. Авторизация (Login)

Было (Single Session): При логине мы удаляли все старые сессии пользователя и создавали одну новую.

-- СТАРАЯ ЛОГИКА
DELETE FROM user_sessions WHERE user_id = ?;
INSERT INTO user_sessions ...

Стало (Multiple Sessions): При логине мы просто создаем новую запись. Старые не трогаем.

-- НОВАЯ ЛОГИКА
INSERT INTO user_sessions (id, user_id, refresh_token, device_info, ip_address, expires_at)
VALUES (uuid_generate_v4(), ?, ?, ?, ?, ?);

Совет:

Парсите User-Agent на бэкенде (например, с помощью библиотеки ua-parser-js или user-agents в Python/Go), чтобы красиво отображать устройство пользователю.


Б. Проверка сессии (Middleware / Auth Guard)

При каждом запросе middleware должен проверять не просто валидность токена, но и его наличие в БД (если используется JWT) или просто искать сессию в Redis/БД.

# Псевдокод проверки сессии
def validate_session(request):
    token = extract_token(request)
    session = db.find_session_by_token(token)

    if not session or session.is_revoked or session.expires_at < now():
        raise UnauthorizedException("Invalid or expired session")

    # Обновляем время последней активности (опционально, но полезно для UI)
    db.update_last_active(session.id)

    return get_user_by_id(session.user_id)

В. Выход (Logout)

Здесь появляется гибкость.

Вам нужно реализовать два эндпоинта:

1. Выход с текущего устройства:

    UPDATE user_sessions SET is_revoked = TRUE WHERE id = ? AND user_id = ?;

2. Выход со всех устройств (Logout everywhere):

    UPDATE user_sessions SET is_revoked = TRUE WHERE user_id = ?;

4. Специфика для JWT (Если вы используете токены)

Если вы используете чистый JWT без Refresh Token, разрешить несколько сессий и при этом иметь возможность их отзывать невозможно без создания "черного списка" (Blacklist), что убивает всю идею stateless.


Правильная архитектура для JWT + Multiple Sessions:

  1. При логине генерируется пара: короткий Access Token (например, на 15 мин) и длинный Refresh Token (на 30 дней).
  2. Refresh Token сохраняется в БД (в таблицу user_sessions).
  3. В Access Token в payload добавляется jti (JWT ID), который равен id записи в user_sessions.
  4. При истечении Access Token клиент идет на эндпоинт /refresh. Бэкенд проверяет Refresh Token в БД. Если он не отозван — выпускает новую пару токенов.
  5. При отзыве сессии мы просто помечаем Refresh Token в БД как отозванный. Следующий раз, когда клиент попытается обновить Access Token, ему будет отказано.

5. Ограничения и Безопасность (Security & Limits)

Разрешая множественные сессии, вы открываете дверь для злоупотреблений.

Обязательно внедрите следующие лимиты:

1. Лимит активных сессий (Max Sessions Limit):

Не позволяйте пользователю иметь 100 активных сессий. Установите лимит (например, 5 или 10 устройств).

Логика:

При создании новой сессии проверяем количество активных. Если лимит превышен, удаляем (или отзываем) самую старую сессию.

    active_sessions = db.count_active_sessions(user_id)
    if active_sessions >= MAX_SESSIONS_LIMIT:
        oldest_session = db.get_oldest_active_session(user_id)
        revoke_session(oldest_session.id)

2. Автоматическая очистка (Cron / TTL):

Если используете Redis, просто ставьте TTL на ключи сессий. Если используете SQL БД, напишите фоновую задачу (Cron), которая раз в сутки удаляет/отзывает сессии, у которых expires_at < NOW().


3. Защита от утечки Refresh Token:

Внедрите Refresh Token Rotation. При каждом обновлении токена старый Refresh Token удаляется и выдается новый. Если сервер замечает, что пытаются использовать уже отработанный (старый) Refresh Token, это значит, что токен украден. В этом случае нужно отозвать все сессии пользователя принудительно.


6. UI/UX: Управление сессиями на фронтенде

Чтобы пользователь чувствовал контроль, добавьте в настройки профиля раздел «Активные сессии» (Active Sessions).

Что отображать:

  • Иконка устройства (Desktop, Mobile, Tablet).
  • Название браузера и ОС (например, Chrome on Windows 10).
  • IP-адрес (или хотя бы город/страну по GeoIP).
  • Время последней активности или дата входа.
  • Метка «Текущее устройство» (Current device).

Действия:

  • Кнопка «Выйти» (Revoke) напротив каждой сессии.
  • Кнопка «Выйти на всех других устройствах» (Logout from all other devices).

Важно:

При нажатии «Выйти» на текущем устройстве, фронтенд должен очистить локальное хранилище (localStorage/cookies) и редиректить на страницу логина.


Чек-лист

  1. [ ] Создать таблицу/коллекцию user_sessions (или refresh_tokens).
  2. [ ] Изменить логику Login: убрать удаление старых сессий, добавить парсинг User-Agent и IP.
  3. [ ] Изменить Middleware: добавить проверку существования сессии в хранилище.
  4. [ ] Реализовать эндпоинт POST /logout (текущая сессия) и POST /logout-all (все сессии).
  5. [ ] Добавить лимит на максимальное количество одновременных сессий (например, 5).
  6. [ ] Написать фоновую задачу для очистки просроченных сессий.
  7. [ ] Реализовать API и UI для отображения списка активных сессий пользователю.

Мы делимся этой технической информацией, чтобы помочь вам в решении задач — используйте её с пониманием. Статья носит рекомендательный характер, поэтому, пожалуйста, применяйте описанные методы осмотрительно.


Статью подготовил: Денис Аверко @Nymexis г. Омск

Комментарии

Загрузка...
Если комментарии не загружаются, можете попробовать отключить блокировщик рекламы для этого сайта