374 lines
12 KiB
Markdown
374 lines
12 KiB
Markdown
|
|
# Система авторизации Discours Core
|
|||
|
|
|
|||
|
|
## 🎯 Обзор архитектуры
|
|||
|
|
|
|||
|
|
Модульная система авторизации с JWT токенами, Redis-сессиями и OAuth интеграцией. Построена на принципах разделения ответственности и высокой производительности.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
auth/
|
|||
|
|
├── tokens/ # 🎯 Система управления токенами
|
|||
|
|
│ ├── sessions.py # JWT сессии с Redis
|
|||
|
|
│ ├── verification.py # Одноразовые токены
|
|||
|
|
│ ├── oauth.py # OAuth токены
|
|||
|
|
│ ├── batch.py # Массовые операции
|
|||
|
|
│ ├── monitoring.py # Мониторинг и статистика
|
|||
|
|
│ ├── storage.py # Фасад для совместимости
|
|||
|
|
│ ├── base.py # Базовые классы
|
|||
|
|
│ └── types.py # Типы и константы
|
|||
|
|
├── middleware.py # HTTP middleware
|
|||
|
|
├── decorators.py # GraphQL декораторы
|
|||
|
|
├── oauth.py # OAuth провайдеры
|
|||
|
|
├── identity.py # Методы идентификации
|
|||
|
|
├── jwtcodec.py # JWT кодек
|
|||
|
|
├── validations.py # Валидация данных
|
|||
|
|
├── credentials.py # Креденшиалы
|
|||
|
|
├── exceptions.py # Исключения
|
|||
|
|
└── utils.py # Утилиты
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🎯 Система токенов
|
|||
|
|
|
|||
|
|
### SessionTokenManager
|
|||
|
|
|
|||
|
|
**Принцип работы:**
|
|||
|
|
1. JWT токены с payload `{user_id, username, iat, exp}`
|
|||
|
|
2. Redis хранение для отзыва и управления жизненным циклом
|
|||
|
|
3. Поддержка множественных сессий на пользователя
|
|||
|
|
4. Автоматическое обновление `last_activity` при активности
|
|||
|
|
|
|||
|
|
**Redis структура:**
|
|||
|
|
```bash
|
|||
|
|
session:{user_id}:{token} # hash с данными сессии
|
|||
|
|
user_sessions:{user_id} # set с активными токенами
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Основные методы:**
|
|||
|
|
```python
|
|||
|
|
from auth.tokens.sessions import SessionTokenManager
|
|||
|
|
|
|||
|
|
sessions = SessionTokenManager()
|
|||
|
|
|
|||
|
|
# Создание сессии
|
|||
|
|
token = await sessions.create_session(user_id, username=username)
|
|||
|
|
|
|||
|
|
# Проверка сессии
|
|||
|
|
payload = await sessions.verify_session(token)
|
|||
|
|
|
|||
|
|
# Обновление сессии
|
|||
|
|
new_token = await sessions.refresh_session(user_id, old_token)
|
|||
|
|
|
|||
|
|
# Отзыв сессии
|
|||
|
|
await sessions.revoke_session_token(token)
|
|||
|
|
|
|||
|
|
# Получение всех сессий пользователя
|
|||
|
|
user_sessions = await sessions.get_user_sessions(user_id)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Типы токенов
|
|||
|
|
|
|||
|
|
| Тип | TTL | Назначение | Менеджер |
|
|||
|
|
|-----|-----|------------|----------|
|
|||
|
|
| `session` | 30 дней | JWT сессии пользователей | `SessionTokenManager` |
|
|||
|
|
| `verification` | 1 час | Одноразовые токены подтверждения | `VerificationTokenManager` |
|
|||
|
|
| `oauth_access` | 1 час | OAuth access токены | `OAuthTokenManager` |
|
|||
|
|
| `oauth_refresh` | 30 дней | OAuth refresh токены | `OAuthTokenManager` |
|
|||
|
|
|
|||
|
|
### Менеджеры токенов
|
|||
|
|
|
|||
|
|
#### 1. **SessionTokenManager** - JWT сессии
|
|||
|
|
```python
|
|||
|
|
from auth.tokens.sessions import SessionTokenManager
|
|||
|
|
|
|||
|
|
sessions = SessionTokenManager()
|
|||
|
|
|
|||
|
|
# Создание сессии
|
|||
|
|
token = await sessions.create_session(
|
|||
|
|
user_id="123",
|
|||
|
|
auth_data={"provider": "local"},
|
|||
|
|
username="john_doe",
|
|||
|
|
device_info={"ip": "192.168.1.1", "user_agent": "Mozilla/5.0"}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Создание JWT токена сессии
|
|||
|
|
token = await sessions.create_session_token(
|
|||
|
|
user_id="123",
|
|||
|
|
token_data={"username": "john_doe", "device_info": "..."}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Проверка сессии (совместимость с TokenStorage)
|
|||
|
|
payload = await sessions.verify_session(token)
|
|||
|
|
# Возвращает: {"user_id": "123", "username": "john_doe", "iat": 1640995200, "exp": 1643587200}
|
|||
|
|
|
|||
|
|
# Валидация токена сессии
|
|||
|
|
valid, data = await sessions.validate_session_token(token)
|
|||
|
|
|
|||
|
|
# Получение данных сессии
|
|||
|
|
session_data = await sessions.get_session_data(token, user_id)
|
|||
|
|
|
|||
|
|
# Обновление сессии
|
|||
|
|
new_token = await sessions.refresh_session(user_id, old_token, device_info)
|
|||
|
|
|
|||
|
|
# Отзыв сессии
|
|||
|
|
await sessions.revoke_session_token(token)
|
|||
|
|
|
|||
|
|
# Отзыв всех сессий пользователя
|
|||
|
|
revoked_count = await sessions.revoke_user_sessions(user_id)
|
|||
|
|
|
|||
|
|
# Получение всех сессий пользователя
|
|||
|
|
user_sessions = await sessions.get_user_sessions(user_id)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2. **VerificationTokenManager** - Одноразовые токены
|
|||
|
|
```python
|
|||
|
|
from auth.tokens.verification import VerificationTokenManager
|
|||
|
|
|
|||
|
|
verification = VerificationTokenManager()
|
|||
|
|
|
|||
|
|
# Создание токена подтверждения email
|
|||
|
|
token = await verification.create_verification_token(
|
|||
|
|
user_id="123",
|
|||
|
|
verification_type="email_change",
|
|||
|
|
data={"new_email": "new@example.com"},
|
|||
|
|
ttl=3600 # 1 час
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Проверка токена
|
|||
|
|
valid, data = await verification.validate_verification_token(token)
|
|||
|
|
|
|||
|
|
# Подтверждение (одноразовое использование)
|
|||
|
|
confirmed_data = await verification.confirm_verification_token(token)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3. **OAuthTokenManager** - OAuth токены
|
|||
|
|
```python
|
|||
|
|
from auth.tokens.oauth import OAuthTokenManager
|
|||
|
|
|
|||
|
|
oauth = OAuthTokenManager()
|
|||
|
|
|
|||
|
|
# Сохранение OAuth токенов
|
|||
|
|
await oauth.store_oauth_tokens(
|
|||
|
|
user_id="123",
|
|||
|
|
provider="google",
|
|||
|
|
access_token="ya29.a0AfH6SM...",
|
|||
|
|
refresh_token="1//04...",
|
|||
|
|
expires_in=3600,
|
|||
|
|
additional_data={"scope": "read write"}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Создание OAuth токена (внутренний метод)
|
|||
|
|
token_key = await oauth._create_oauth_token(
|
|||
|
|
user_id="123",
|
|||
|
|
token_data={"token": "ya29.a0AfH6SM...", "provider": "google"},
|
|||
|
|
ttl=3600,
|
|||
|
|
provider="google",
|
|||
|
|
token_type="oauth_access"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Получение access токена
|
|||
|
|
access_data = await oauth.get_token(user_id, "google", "oauth_access")
|
|||
|
|
|
|||
|
|
# Оптимизированное получение OAuth данных
|
|||
|
|
oauth_data = await oauth._get_oauth_data_optimized("oauth_access", "123", "google")
|
|||
|
|
|
|||
|
|
# Отзыв OAuth токенов
|
|||
|
|
await oauth.revoke_oauth_tokens(user_id, "google")
|
|||
|
|
|
|||
|
|
# Оптимизированный отзыв токена
|
|||
|
|
revoked = await oauth._revoke_oauth_token_optimized("oauth_access", "123", "google")
|
|||
|
|
|
|||
|
|
# Отзыв всех OAuth токенов пользователя
|
|||
|
|
revoked_count = await oauth.revoke_user_oauth_tokens(user_id, "oauth_access")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 4. **BatchTokenOperations** - Массовые операции
|
|||
|
|
```python
|
|||
|
|
from auth.tokens.batch import BatchTokenOperations
|
|||
|
|
|
|||
|
|
batch = BatchTokenOperations()
|
|||
|
|
|
|||
|
|
# Массовая проверка токенов
|
|||
|
|
tokens = ["token1", "token2", "token3"]
|
|||
|
|
results = await batch.batch_validate_tokens(tokens)
|
|||
|
|
# {"token1": True, "token2": False, "token3": True}
|
|||
|
|
|
|||
|
|
# Валидация батча токенов (внутренний метод)
|
|||
|
|
batch_results = await batch._validate_token_batch(tokens)
|
|||
|
|
|
|||
|
|
# Безопасное декодирование токена
|
|||
|
|
payload = await batch._safe_decode_token(token)
|
|||
|
|
|
|||
|
|
# Массовый отзыв токенов
|
|||
|
|
revoked_count = await batch.batch_revoke_tokens(tokens)
|
|||
|
|
|
|||
|
|
# Отзыв батча токенов (внутренний метод)
|
|||
|
|
batch_revoked = await batch._revoke_token_batch(tokens)
|
|||
|
|
|
|||
|
|
# Очистка истекших токенов
|
|||
|
|
cleaned_count = await batch.cleanup_expired_tokens()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 5. **TokenMonitoring** - Мониторинг
|
|||
|
|
```python
|
|||
|
|
from auth.tokens.monitoring import TokenMonitoring
|
|||
|
|
|
|||
|
|
monitoring = TokenMonitoring()
|
|||
|
|
|
|||
|
|
# Статистика токенов
|
|||
|
|
stats = await monitoring.get_token_statistics()
|
|||
|
|
# {
|
|||
|
|
# "session_tokens": 150,
|
|||
|
|
# "verification_tokens": 5,
|
|||
|
|
# "oauth_access_tokens": 25,
|
|||
|
|
# "oauth_refresh_tokens": 25,
|
|||
|
|
# "memory_usage": 1048576
|
|||
|
|
# }
|
|||
|
|
|
|||
|
|
# Подсчет ключей по паттерну (внутренний метод)
|
|||
|
|
count = await monitoring._count_keys_by_pattern("session:*")
|
|||
|
|
|
|||
|
|
# Health check
|
|||
|
|
health = await monitoring.health_check()
|
|||
|
|
# {"status": "healthy", "redis_connected": True, "token_count": 205}
|
|||
|
|
|
|||
|
|
# Оптимизация памяти
|
|||
|
|
optimization = await monitoring.optimize_memory_usage()
|
|||
|
|
# {"cleaned_expired": 10, "memory_freed": 102400}
|
|||
|
|
|
|||
|
|
# Оптимизация структур данных (внутренний метод)
|
|||
|
|
optimized = await monitoring._optimize_data_structures()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### TokenStorage (Фасад для совместимости)
|
|||
|
|
```python
|
|||
|
|
from auth.tokens.storage import TokenStorage
|
|||
|
|
|
|||
|
|
# Упрощенный API для основных операций
|
|||
|
|
await TokenStorage.create_session(user_id, username=username)
|
|||
|
|
await TokenStorage.verify_session(token)
|
|||
|
|
await TokenStorage.refresh_session(user_id, old_token, device_info)
|
|||
|
|
await TokenStorage.revoke_session(token)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🔧 Middleware и декораторы
|
|||
|
|
|
|||
|
|
### AuthMiddleware
|
|||
|
|
```python
|
|||
|
|
from auth.middleware import AuthMiddleware
|
|||
|
|
|
|||
|
|
# Автоматическая обработка токенов
|
|||
|
|
middleware = AuthMiddleware()
|
|||
|
|
|
|||
|
|
# Извлечение токена из запроса
|
|||
|
|
token = await extract_token_from_request(request)
|
|||
|
|
|
|||
|
|
# Проверка сессии
|
|||
|
|
payload = await sessions.verify_session(token)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### GraphQL декораторы
|
|||
|
|
```python
|
|||
|
|
from auth.decorators import auth_required, permission_required
|
|||
|
|
|
|||
|
|
@auth_required
|
|||
|
|
async def protected_resolver(info, **kwargs):
|
|||
|
|
"""Требует авторизации"""
|
|||
|
|
user = info.context.get('user')
|
|||
|
|
return f"Hello, {user.username}!"
|
|||
|
|
|
|||
|
|
@permission_required("shout:create")
|
|||
|
|
async def create_shout(info, input_data):
|
|||
|
|
"""Требует права на создание публикаций"""
|
|||
|
|
pass
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## ORM модели
|
|||
|
|
|
|||
|
|
### Author (Пользователь)
|
|||
|
|
```python
|
|||
|
|
class Author:
|
|||
|
|
id: int
|
|||
|
|
email: str
|
|||
|
|
name: str
|
|||
|
|
slug: str
|
|||
|
|
password: Optional[str] # bcrypt hash
|
|||
|
|
pic: Optional[str] # URL аватара
|
|||
|
|
bio: Optional[str]
|
|||
|
|
email_verified: bool
|
|||
|
|
phone_verified: bool
|
|||
|
|
created_at: int
|
|||
|
|
updated_at: int
|
|||
|
|
last_seen: int
|
|||
|
|
|
|||
|
|
# OAuth данные в JSON формате
|
|||
|
|
oauth: Optional[dict] # {"google": {"id": "123", "email": "user@gmail.com"}}
|
|||
|
|
|
|||
|
|
# Поля аутентификации
|
|||
|
|
failed_login_attempts: int
|
|||
|
|
account_locked_until: Optional[int]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### OAuth данные
|
|||
|
|
OAuth данные хранятся в JSON поле `oauth` модели `Author`:
|
|||
|
|
```python
|
|||
|
|
# Формат oauth поля
|
|||
|
|
{
|
|||
|
|
"google": {
|
|||
|
|
"id": "123456789",
|
|||
|
|
"email": "user@gmail.com",
|
|||
|
|
"name": "John Doe"
|
|||
|
|
},
|
|||
|
|
"github": {
|
|||
|
|
"id": "456789",
|
|||
|
|
"login": "johndoe",
|
|||
|
|
"email": "user@github.com"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## ⚙️ Конфигурация
|
|||
|
|
|
|||
|
|
### Переменные окружения
|
|||
|
|
```bash
|
|||
|
|
# JWT настройки
|
|||
|
|
JWT_SECRET=your_super_secret_key
|
|||
|
|
JWT_EXPIRATION_HOURS=720 # 30 дней
|
|||
|
|
|
|||
|
|
# Redis подключение
|
|||
|
|
REDIS_URL=redis://localhost:6379/0
|
|||
|
|
|
|||
|
|
# OAuth провайдеры
|
|||
|
|
GOOGLE_CLIENT_ID=your_google_client_id
|
|||
|
|
GOOGLE_CLIENT_SECRET=your_google_client_secret
|
|||
|
|
GITHUB_CLIENT_ID=your_github_client_id
|
|||
|
|
GITHUB_CLIENT_SECRET=your_github_client_secret
|
|||
|
|
FACEBOOK_APP_ID=your_facebook_app_id
|
|||
|
|
FACEBOOK_APP_SECRET=your_facebook_app_secret
|
|||
|
|
VK_APP_ID=your_vk_app_id
|
|||
|
|
VK_APP_SECRET=your_vk_app_secret
|
|||
|
|
YANDEX_CLIENT_ID=your_yandex_client_id
|
|||
|
|
YANDEX_CLIENT_SECRET=your_yandex_client_secret
|
|||
|
|
|
|||
|
|
# Session cookies
|
|||
|
|
SESSION_COOKIE_NAME=session_token
|
|||
|
|
SESSION_COOKIE_SECURE=true
|
|||
|
|
SESSION_COOKIE_HTTPONLY=true
|
|||
|
|
SESSION_COOKIE_SAMESITE=lax
|
|||
|
|
SESSION_COOKIE_MAX_AGE=2592000 # 30 дней
|
|||
|
|
|
|||
|
|
# Frontend
|
|||
|
|
FRONTEND_URL=https://yourdomain.com
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Производительность
|
|||
|
|
|
|||
|
|
### Оптимизации Redis
|
|||
|
|
- **Pipeline операции** для атомарности
|
|||
|
|
- **Batch обработка** токенов (100-1000 за раз)
|
|||
|
|
- **SCAN** вместо KEYS для безопасности
|
|||
|
|
- **TTL** автоматическая очистка
|
|||
|
|
|
|||
|
|
### Кэширование
|
|||
|
|
- **@lru_cache** для часто используемых ключей
|
|||
|
|
- **Connection pooling** для Redis
|
|||
|
|
- **JWT decode caching** в middleware
|