1. «Я вошёл в систему, а меня не узнают?». Как всё началось
OAuth2, JWT, сессионная аутентификация… Способов аутентификации множество, и в большинстве случаев их достаточно. Я тоже так думал.
- Когда я добавлял OAuth2 в собственный почтовый клиент или в MyGPT от ChatGPT, это обеспечивало отличный пользовательский опыт;
- Для одностраничных Django‑приложений лучшим вариантом оказалась сессионная аутентификация;
- При разделённой архитектуре фронтенд/бэкенд самым чистым решением выглядел JWT.
Однако одна комбинация однажды дала сбой – асинхронные задачи (Celery). Пользователь нажимает кнопку, а вместо того, чтобы бекенд сразу обработал запрос, работа передаётся удалённому AI‑серверу или воркеру. И воркер спрашивает:
«Эй… запрос получен, но для кого? request.user у меня нет».

2. Проблема «бэкенд ↔ бэкенд» + «асинхронный воркер (Celery)»
Ключевой момент, заставивший меня внедрить API‑ключ, заключался в том, что между бэкенд‑серверами появлялся Celery‑воркер.
- Пользователь отправляет HTTP‑запрос;
- Бэкенд кладёт задачу в очередь;
- Celery‑воркер берёт задачу и отправляет асинхронный запрос на какой‑то вычислительный сервер.
Главная проблема в этой цепочке:
- У воркера нет request.user;
- Нет сессии (это не браузер);
- JWT сложно использовать – непонятно, кто, где и как будет хранить и передавать токен;
- OAuth2 требует пользовательского взаимодействия и просто не подходит.
Когда JWT и сессии становятся неэффективными, остаётся один вопрос:
«Как воркеру указать, от имени какого клиента (тенанта/пользователя) выполнять задачу?»
3. Для воркера важнее «идентификация», чем «аутентификация»
В веб‑запросах под аутентификацией обычно подразумевается вход пользователя в систему. Воркер же – это не человек, а приложение, которое самостоятельно выполняет задачи на процессоре. Поэтому сначала нужна идентификация, а аутентификацию можно обеспечить с помощью простых HMAC или secret‑key.
- Задача должна выполняться с данными клиента A;
- Результат должен сохраняться в ресурсах клиента A;
- Тариф, права и квоты учитываются по клиенту A.
Попытка интегрировать JWT или сессию в эту схему приводит к росту сложности: генерация, хранение, передача, истечение, обновление токенов. И возникает непреодолимое чувство: «Какой смысл выдавать JWT серверу, если пользователь не использует браузер прямо сейчас?» – идея была быстро отвергнута.
4. Решение: API‑ключ оказался простым и мощным
Мы внедрили API‑ключ и сразу решили проблему.
- Воркер при внутреннем запросе передаёт один заголовок для аутентификации и идентификации;
- Сервер по ключу мгновенно определяет, чей это запрос;
- Обновление/замена ключа (ротация) становится предельно прозрачной.
Пример запроса к вычислительному серверу:
POST /v1/ai/jobs
Authorization: Api-Key <KEY>
Content-Type: application/json
{ "job_id": "...", "payload": {...} }
Воркеру не нужен request.user – достаточно отправить запрос с API‑ключом, а принимающий бэкенд определит пользователя по этому ключу.
5. Ключи, привязанные к пользователям, упростили эксплуатацию
Меня особенно порадовал тот факт, что обычные библиотеки вроде rest_framework_api_key предоставляют функциональность только для самого ключа, но в моём случае важно было связать «ключ ↔ пользователь (клиент)».
Мы унаследовали AbstractAPIKey и создали CustomAPIKey, связав её внешним ключом с моделью AUTH_USER:
from django.conf import settings
from rest_framework_api_key.models import AbstractAPIKey
from django.db import models
class CustomAPIKey(AbstractAPIKey):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="api_keys"
)
is_test = models.BooleanField(default=False) # для тестовых/стейджинг‑ключей
Эта привязка открыла массу возможностей, выходящих за рамки простой аутентификации.
6. Автоматическая выдача ключей per‑user и её операционные плюсы
При регистрации пользователю автоматически создаётся ключ. Это дало несколько преимуществ.
1) Управление валидностью стало простым
- Можно быстро просмотреть, деактивировать или удалить ключ конкретного пользователя;
- При удалении пользователя связанные ключи исчезают автоматически (FK cascade).
2) Ротация ключей стала тривиальной
- При подозрении на компрометацию достаточно создать новый ключ и отозвать старый;
- Поддержка нескольких ключей позволяет осуществлять безотказную замену (новый ключ включён, старый постепенно выключается).
3) Тарифы/квоты/права теперь привязаны к пользователю
- Ограничения задаются не по ключу, а по пользователю, что упрощает логику биллинга;
- Не нужно каждый раз определять, чей запрос исходит от конкретного ключа.
4) Один пользователь может иметь несколько ключей
Флаг is_test демонстрирует свои преимущества. Поскольку связь не OneToOne, а FK, один пользователь может иметь несколько ключей для разных целей:
- Тестовый ключ для стейджинга;
- Продакшн‑ключ для реального использования;
- Разделение логов и мониторинга между тестовым и боевым трафиком.
7. Выбор аутентификационного механизма – не вопрос «лучшего», а вопрос «подходящего»
Выводы, которые я считаю оптимальными:
- OAuth2 – для интеграций с внешними сервисами, когда важен пользовательский consent;
- Сессионная аутентификация – для одиночных Django‑приложений, где важна скорость разработки и простота;
- JWT – для разделённых фронтенд/бэкенд, мобильных и SPA‑клиентов;
- API‑ключ – для бекенд‑бекенд, автоматизации, воркеров и батч‑заданий, где запросы инициируются не пользователем.
Как только в цепочку попадает Celery‑воркер, попытка «унифицировать всё через логин» только усложняет систему. Здесь API‑ключ стал идеальным спасением.
8. Заключение
Для людей (браузеров, приложений) естественно использовать сессии, JWT или OAuth2. Воркер же – процесс, которому нужно знать, чей это запрос. Мы перешли на API‑ключ не из-за избыточной безопасности, а потому что это было самое простое решение в данном контексте. Привязав ключи к пользователям, мы превратили их из просто средства доступа в полноценный операционный рычаг.
А вы часто используете API‑ключи? Я считаю, что даже ограниченное их применение может принести большую пользу. Надеюсь, мой опыт вдохновит вас на более гибкие решения.
Связанные статьи
Комментариев нет.