Ниже — подробная, практическая инструкция по проектированию и реализации надёжной системы авторизации для продакшн‑сайта (бекенд на Python). Я дам архитектурные соображения, пошаговые алгоритмы для основных сценариев (регистрация, вход, refresh, логаут, сброс пароля, подтверждение e‑mail), рекомендации по безопасности, конкретные библиотеки и примеры кода/моделей. Материал достаточно подробный, чтобы вы могли спроектировать и реализовать систему или оценить имеющуюся.
Краткое содержание (быстро ориентироваться)
- Цели: безопасность, отказоустойчивость, управляемость, масштабируемость.
- Выбор подхода: stateful (сессии) vs stateless (JWT) — сочетание: короткие access‑токены (JWT) + stateful refresh‑токены.
- Пароли: Argon2 (или bcrypt), соль + (опционально) “pepper”, миграция/версионирование хешей.
- Токены: access (кратковременный, JWT), refresh (длинный, одноразовый/ротируемый, хранимый в БД/Redis).
- Безопасность передачи: HTTPS, Secure, HttpOnly, SameSite куки; защита от CSRF/CORS.
- MFA, логирование, мониторинг, рейт‑лимитинг, блокировки.
- Рекомендуемые библиотеки: FastAPI/Django/Flask + SQLAlchemy/Django ORM + argon2‑cffi + PyJWT/authlib + passlib + redis.
1. Архитектурный выбор и общий подход
- Уровни:
- Клиент (SPA/mobile/SSR)
- API‑gateway / backend
- База данных (пользователи, состояния refresh)
- Кеш/стейт (Redis) для rate‑limit, токен‑ревайв/блоклистов
- SMTP/queue для писем, лог/мониторинг
- Рекомендуемая стратегия: гибрид — короткоживущий access‑токен (JWT, ~1–15 мин) + долгоживущий refresh‑токен (~7–30 дней) с хранением/ротацией на сервере.
- Почему: JWT позволяет быстро валидировать access без обращения к БД; refresh‑токен позволяет безопасно обновлять access и обеспечивает контроль (реvoke, logout, single sign‑out).
- Альтернатива: stateful сессионные куки (хорошо для классических вебов) — проще с CSRF/сессионным хранилищем (Redis). Для SPA/мобильных часто используют JWT + refresh.
2. Схема данных (пример)
- users:
- id (UUID)
- email (unique)
- password_hash
- password_hash_algo_version
- is_active, is_verified (email)
- created_at, updated_at, last_login_at
- mfa_enabled, mfa_secret (TOTP) — зашифрованно
- role / permissions (RBAC fields)
- refresh_tokens (или sessions):
- id (UUID)
- user_id
- refresh_token_hash (hash от токена, не хранить plain)
- jti (JWT id) или token_id
- created_at, expires_at, last_used_at
- ip, user_agent, device_name
- revoked_at, revoke_reason
- rotation_counter (если используете rotation)
- token_blacklist (опционально): jti или token_id с expiry (Redis)
3. Хранение паролей
- Использовать Argon2id (рекомендация) или bcrypt. Не храните SHA, MD5, plain.
- Использовать библиотеку argon2‑cffi или passlib.
- Параметры: подобрать параметры памяти/времени в зависимости от инфраструктуры. Пример: time_cost=2–4, memory_cost=65536 (64MB), parallelism=4 — подберите нагрузочный профиль.
- Всегда использовать соль (библиотеки это делают).
- Храните также версию алгоритма (для миграции), и опционально — pepper (секрет приложения, хранить в vault).
Пример хеширования (argon2‑cffi):
from argon2 import PasswordHasher
ph = PasswordHasher(time_cost=3, memory_cost=65536, parallelism=4)
hash = ph.hash(plaintext_password)
ph.verify(hash, candidate)
4. Регистрация и подтверждение e‑mail (flow)
1) Пользователь отправляет POST /register {email, password, ...}.
2) Валидируете e‑mail, пароль (длина, entropy), проверяете дубли.
3) Хешируете пароль (см. выше) и сохраняете пользователя с is_verified=false.
4) Генерируете verification token (одноразовый JWT или random string) с expiry (24h). Сохраняете token_hash в БД или подписываете JWT с claim type=verify_email.
5) Отправляете письмо со ссылкой: https://site/verify?token=XXX.
6) Нажатие → сервер валидирует token, помечает is_verified=true, инвалидирует токен.
Замечания: ограничьте частоту повторных запросов, используйте CAPTCHА при необходимости.
5. Вход (login) — рекомендуемый flow (access + refresh)
1) POST /login {email, password}:
- Проверить блокировки и rate‑limit по IP/учётке.
- Найти пользователя, проверить password via argon2.verify.
- Убедиться is_active и is_verified (или разрешить unverified с ограничениями).
2) Если MFA включен — инициировать 2FA (TOTP or push) → возвращаете temporary session/OTP challenge.
3) На успешную аутентификацию:
- Сгенерировать access_token (JWT) с коротким exp (например 5–15 мин). Claims: sub=user_id, iss, aud, exp, iat, jti, scope/roles.
- Сгенерировать refresh_token — длинный случайный string (cryptographically secure, 128+ бит), не JWT (или можно быть JWT, но store server side).
- Hash(refresh_token) и сохраните в таблице refresh_tokens с meta (ip, ua, device, expires_at).
4) Вернуть:
- В API‑only (SPA/mobile): access_token в ответе, refresh_token — лучше вернуть HttpOnly Secure cookie; или если в body — хранить в secure storage.
- В классическом web: Set‑Cookie HttpOnly Secure SameSite=Strict для refresh, а access можно хранить в памяти.
Почему хешировать refresh: если DB утечёт — нельзя использовать реальные refresh‑токены.
6. Access token (JWT) — рекомендации
- Используйте минимальные привелегии в claims.
- Обязательные claims: iss (issuer), sub (user id), aud (audience), exp, iat, jti.
- Подпись: предпочтительно RS256 (асимметричная) — приватный ключ для подписи, публичный для верификации (полезно при микро‑сервисах). HS256 проще (секрет), но ключ должен быть защищённый.
- Хранение в браузере: не в localStorage (XSS риск). Лучше хранить access в памяти и refresh в HttpOnly cookie. Если храните access в cookie — добавьте CSRF защиту.
- Expiration: короткий — 5–15 минут для чувствительных систем; 30–60 минут для менее чувствительных. Чем короче — тем меньше риск при утечке.
- Если хотите возможность немедленного отката access (logout/принудительное блокирование), нужно либо:
- хранить jti/allowlist/denylist в Redis (expirable) и проверять при каждой валидации, или
- использовать короткий exp (и на revoke полагаться на refresh revocation).
7. Refresh token — безопасная реализация и ротация
- Генерация: cryptographically secure random string (например 32+ байт, base64).
- Хранение: только hash(refresh_token) в БД (использовать SHA‑256).
- Rotation (рекомендуется): при каждом использовании refresh выдавайте новый refresh_token и помечайте старый как использованный/revoked.
- Это предотвращает повторное использование украденного refresh (replay). Алгоритм:
1) Клиент посылает refresh_token.
2) Сервер хеширует и находит запись; если найден и not revoked, создать новый refresh_token, сохранить новый hash, пометить старый revoked (и пометить last_used).
3) Если старый уже был использован (повторный usage) — это индикатор компрометации: инвалидация всех токенов этого пользователя и/или потребовать re-login.
- Дополнительно: храните привязку к device/ip для обнаружения аномалий.
- TTL: refresh lifetime (например 7–30 дней), но с ротацией и inactivity expiry.
- При logout: удаляете/ревокируете refresh token(s).
8. Logout и аннулирование
- Для logout: удалить/ревокировать refresh token(s) в БД и клиенту удалить куки.
- Для полного удаления сессий (admin action): отметить все refresh tokens пользователя revoked.
- Для немедленного аннулирования access токенов — нужно держать blacklist jti в Redis до истечения exp или принуждать клиентов re‑login по def — tradeoff.
9. Защита от CSRF и XSS
- Если вы храните auth в cookie:
- Use HttpOnly, Secure, SameSite=Strict/Lax, set path/domain appropriately.
- Добавьте CSRF токен (например Double Submit Cookie или синхронный запрос) для state‑changing запросов.
- Если храните access в JS (memory/localStorage) — XSS риск. Минимизируйте XSS (CSP, валидация, код review).
- CORS: выставляйте точные origins, не wildcard Access-Control-Allow-Origin.
10. MFA (2FA)
- TOTP (RFC6238) — используйте библиотеку pyotp. Секрет храните зашифрованный (KMS) и помечайте mfa_enabled.
- Flow:
- При логине, после пароля — если mfa_enabled -> требовать TOTP код, либо у вас step-up authentication.
- При активации — показываете QR, проверяете одноразовый код.
- Альтернативы: push notifications, WebAuthn (FIDO2) — рекомендуются для высокой безопасности.
11. OAuth2 / OpenID Connect
- Если нужно SSO/внешняя авторизация — использовать стандарты OAuth2/OIDC. Библиотеки: authlib, python‑oauthlib, oryd/hydra (external).
- Рекомендуется не писать собственный OAuth2 provider без глубокого понимания. Используйте battle‑tested libs.
12. Логирование и мониторинг
- Логируйте события безопасности: login success/fail, refresh usage, password change, IP, user agent.
- Настройте оповещения на аномалии: множество неудачных попыток, reuse refresh.
- Храните логи в централизованном хранилище (SIEM), защитите логи.
13. Rate limiting и защита от брутфорса
- Rate limit по IP и по учётке. Используйте Redis/token bucket.
- Lockout policy: временная блокировка после X неудачных попыток (например 5), с экспоненциальным ростом timeout.
- CAPTCHA для подозрительного поведения.
14. Секреты и ключи
- Храните приватные ключи и pepper в Vault (HashiCorp Vault, AWS KMS/Secrets Manager).
- Подписывайте JWT приватным ключом; публикуйте JWKS (JSON Web Key Set) если нужны другие сервисы.
- Ключи: настройка ротации; версия/ключ_id в JWT header (kid).
15. Тестирование, ревью, audit
- Покрытие unit и интеграционными тестами (авторизация, expiry, revocation, race conditions).
- Проверьте edge cases: replay, race при rotation (атомарность операций).
- Проведите security review и penetration testing.
- Используйте SAST/DAST.
16. Дополнительные принципы и hardening
- Минимизируйте поля в JWT (не включайте чувствительные данные).
- Защита от replay: jti + проверка uniqueness/allowlist.
- Сессии: минимальный объём meta, удалять старые записи.
- Пользовательские права: RBAC / ABAC — реализация проверки прав лежит в API.
- Политики паролей, история паролей, принудительное обновление.
- GDPR/Privacy: храните минимум данных, удаление по запросу.
17. Практическая последовательность разработки (пошаговая)
1) Выберите стек: (FastAPI/Flask/Django). Для API→FastAPI хорошо + OAuth2 utilities.
2) Настройте базовую модель users (with password_hash, version).
3) Реализуйте регистрацию + confirmation (добавьте queue/email).
4) Реализуйте безопасное хеширование паролей (argon2).
5) Реализуйте login endpoint, генерацию access JWT + refresh token:
- создать таблицу refresh_tokens (хранить hash).
- реализовать выдачу cookie/response.
6) Реализуйте middleware/depends для валидации access JWT:
- проверка подписи, exp, iss/aud, jti валидность (при необходимости check revoke list).
7) Реализуйте /token/refresh с rotation:
- атомарная транзакция: найти запись refresh_hash, проверить, создать новый hash и сохранить, пометить старый как revoked.
- возвращать новый pair access+refresh.
8) Логаут: revoke refresh token.
9) Добавить MFA как опциональный шаг.
10) Ввести rate limiting, logging.
11) Провести тесты и подготовить к деплою: ключи в vault, HTTPS, HSTS.
18. Примеры кода (микро‑фрагменты)
- Хеш пароля:
from argon2 import PasswordHasher
ph = PasswordHasher(time_cost=3, memory_cost=65536, parallelism=4)
password_hash = ph.hash(password)
ph.verify(password_hash, candidate_password)
- Генерация refresh token:
import secrets, hashlib
raw = secrets.token_urlsafe(64) # длинная строка
token_hash = hashlib.sha256(raw.encode()).hexdigest()
# храните token_hash, а raw — отдаёте клиенту
- Подпись JWT (RS256) с PyJWT:
import jwt, datetime
private_key = open('priv.pem').read()
payload = {"sub": str(user_id), "iss": "https://api.example.com", "aud": "example", "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=15), "iat": datetime.datetime.utcnow(), "jti": some_uuid}
token = jwt.encode(payload, private_key, algorithm="RS256", headers={"kid": "v1"})
# верификация с публичным ключом
19. Ротация refresh: сценарий обработки атак
- Клиент A получает refresh R1; attacker получает R1; клиент A направляет R1 для обновления → сервер увидит R1, выдаст R2 и пометит R1 использованным. Если attacker повторно присылает R1 — сервер видит, что R1 уже использован → treat as possible theft:
- revoke all sessions for this user;
- уведомить пользователя, требовать re‑auth и (опционально) force password reset;
- логировать и оповестить security team.
20. Внедрение и деплой
- HTTPS обязательно (TLS 1.2+).
- Храните приватные ключи в KMS/Vault.
- Настройте rate limiter, WAF, мониторинг.
- Настройте health checks, graceful shutdown.
- План ротации ключей и механизм отката.
21. Библиотеки / готовые решения
- Django: встроенная auth, django-rest-framework-simplejwt (JWT), django-axes (rate limit). Django может закрыть большую часть.
- FastAPI: fastapi.security.OAuth2PasswordBearer, PyJWT, authlib, passlib/argon2.
- Auth as a Service: Auth0, Okta, Keycloak (open source) — для сложных/корпоративных требований рассмотрите отдачу аутентификации провайдеру.
22. Сводка рекомендаций
- Хешируйте пароли Argon2; не храните plain.
- Используйте короткие JWT и долго живущие refresh c ротацией.
- Храните только хеши refresh токенов, логируйте все подозрительные события.
- Защищайте cookies (HttpOnly, Secure, SameSite), используйте CSRF токены.
- Внедрите MFA для повышенной безопасности.
- Обеспечьте мониторинг, оповещение и средства аннулирования сессий.
- Храните секреты в Vault, имейте план ротации ключей.
Если хотите, могу:
- Прислать готовую структуру endpoint'ов (OpenAPI) и пример реализации для FastAPI + SQLAlchemy + Redis c кодом для /register, /login, /refresh, /logout, /verify_email, /password_reset.
- Помочь подобрать параметры Argon2 под вашу infra.
- Нарисовать step-by-step sequence diagrams для login/refresh flows.
Напишите, какой стек вы планируете (FastAPI/Django/Flask), нужен ли обработчик SPA/mobile, и хотите ли пример кода конкретно для FastAPI — подготовлю рабочий пример.