Files
core/docs/auth/microservices.md
Untone 14ff155789
All checks were successful
Deploy on push / deploy (push) Successful in 3m19s
config-fix
2025-09-30 21:48:29 +03:00

547 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 🔍 Аутентификация для микросервисов
## 🎯 Обзор
Руководство по интеграции системы аутентификации Discours Core с другими микросервисами через общий Redis connection pool.
## 🚀 Быстрый старт
### Подключение к Redis
```python
# Используйте тот же Redis connection pool
from storage.redis import redis
# Или создайте свой с теми же настройками
import aioredis
redis_client = aioredis.from_url(
"redis://localhost:6379/0",
max_connections=20,
retry_on_timeout=True,
socket_keepalive=True,
socket_keepalive_options={},
health_check_interval=30
)
```
### Проверка токена сессии
```python
from auth.tokens.sessions import SessionTokenManager
from auth.utils import extract_token_from_request
async def check_user_session(request) -> dict | None:
"""Проверка сессии пользователя в микросервисе"""
# 1. Извлекаем токен из запроса
token = await extract_token_from_request(request)
if not token:
return None
# 2. Проверяем сессию через SessionTokenManager
sessions = SessionTokenManager()
payload = await sessions.verify_session(token)
if payload:
return {
"authenticated": True,
"user_id": payload.get("user_id"),
"username": payload.get("username"),
"expires_at": payload.get("exp")
}
return {"authenticated": False, "error": "Invalid token"}
```
## 🔑 Redis ключи для поиска
### Структура данных
```bash
# Сессии пользователей
session:{user_id}:{token} # Hash: {user_id, username, device_info, last_activity}
user_sessions:{user_id} # Set: {token1, token2, ...}
# OAuth токены
oauth_access:{user_id}:{provider} # JSON: {token, expires_in, scope}
oauth_refresh:{user_id}:{provider} # JSON: {token, provider_data}
# Токены подтверждения
verification_token:{token} # JSON: {user_id, type, data, created_at}
# OAuth состояние
oauth_state:{state} # JSON: {provider, redirect_uri, code_verifier}
```
### Примеры поиска
```python
from storage.redis import redis
# 1. Поиск всех сессий пользователя
async def get_user_sessions(user_id: int) -> list[str]:
"""Получить все активные токены пользователя"""
session_key = f"user_sessions:{user_id}"
tokens = await redis.smembers(session_key)
return [token.decode() for token in tokens] if tokens else []
# 2. Получение данных конкретной сессии
async def get_session_data(user_id: int, token: str) -> dict | None:
"""Получить данные сессии"""
session_key = f"session:{user_id}:{token}"
data = await redis.hgetall(session_key)
if data:
return {k.decode(): v.decode() for k, v in data.items()}
return None
# 3. Проверка существования токена
async def token_exists(user_id: int, token: str) -> bool:
"""Проверить существование токена"""
session_key = f"session:{user_id}:{token}"
return await redis.exists(session_key)
# 4. Получение TTL токена
async def get_token_ttl(user_id: int, token: str) -> int:
"""Получить время жизни токена в секундах"""
session_key = f"session:{user_id}:{token}"
return await redis.ttl(session_key)
```
## 🛠️ Методы интеграции
### 1. Прямая проверка токена
```python
from auth.tokens.sessions import SessionTokenManager
async def authenticate_request(request) -> dict:
"""Аутентификация запроса в микросервисе"""
sessions = SessionTokenManager()
# Извлекаем токен
token = await extract_token_from_request(request)
if not token:
return {"authenticated": False, "error": "No token provided"}
try:
# Проверяем JWT и Redis сессию
payload = await sessions.verify_session(token)
if payload:
user_id = payload.get("user_id")
# Дополнительно получаем данные сессии из Redis
session_data = await sessions.get_session_data(token, user_id)
return {
"authenticated": True,
"user_id": user_id,
"username": payload.get("username"),
"session_data": session_data,
"expires_at": payload.get("exp")
}
else:
return {"authenticated": False, "error": "Invalid or expired token"}
except Exception as e:
return {"authenticated": False, "error": f"Authentication error: {str(e)}"}
```
### 2. Массовая проверка токенов
```python
from auth.tokens.batch import BatchTokenOperations
async def validate_multiple_tokens(tokens: list[str]) -> dict[str, bool]:
"""Массовая проверка токенов для API gateway"""
batch = BatchTokenOperations()
return await batch.batch_validate_tokens(tokens)
# Использование
async def api_gateway_auth(request_tokens: list[str]):
"""Пример использования в API Gateway"""
results = await validate_multiple_tokens(request_tokens)
authenticated_requests = []
for token, is_valid in results.items():
if is_valid:
# Получаем данные пользователя для валидных токенов
sessions = SessionTokenManager()
payload = await sessions.verify_session(token)
if payload:
authenticated_requests.append({
"token": token,
"user_id": payload.get("user_id"),
"username": payload.get("username")
})
return authenticated_requests
```
### 3. Получение данных пользователя
```python
from auth.utils import get_user_data_by_token
async def get_user_info(token: str) -> dict | None:
"""Получить информацию о пользователе по токену"""
try:
user_data = await get_user_data_by_token(token)
return user_data
except Exception as e:
print(f"Ошибка получения данных пользователя: {e}")
return None
# Использование
async def protected_endpoint(request):
"""Пример защищенного endpoint в микросервисе"""
token = await extract_token_from_request(request)
user_info = await get_user_info(token)
if not user_info:
return {"error": "Unauthorized", "status": 401}
return {
"message": f"Hello, {user_info.get('username')}!",
"user_id": user_info.get("user_id"),
"status": 200
}
```
## 🔧 HTTP заголовки и извлечение токенов
### Поддерживаемые форматы
```python
from auth.utils import extract_token_from_request, get_safe_headers
async def extract_auth_token(request) -> str | None:
"""Извлечение токена из различных источников"""
# 1. Автоматическое извлечение (рекомендуется)
token = await extract_token_from_request(request)
if token:
return token
# 2. Ручное извлечение из заголовков
headers = get_safe_headers(request)
# Bearer токен в Authorization
auth_header = headers.get("authorization", "")
if auth_header.startswith("Bearer "):
return auth_header[7:].strip()
# Кастомный заголовок X-Session-Token
session_token = headers.get("x-session-token")
if session_token:
return session_token.strip()
# Cookie (для веб-приложений)
if hasattr(request, "cookies"):
cookie_token = request.cookies.get("session_token")
if cookie_token:
return cookie_token
return None
```
### Примеры HTTP запросов
```bash
# 1. Bearer токен в Authorization header
curl -H "Authorization: Bearer your_jwt_token_here" \
http://localhost:8000/api/protected
# 2. Кастомный заголовок
curl -H "X-Session-Token: your_jwt_token_here" \
http://localhost:8000/api/protected
# 3. Cookie (автоматически для веб-приложений)
curl -b "session_token=your_jwt_token_here" \
http://localhost:8000/api/protected
```
## 📊 Мониторинг и статистика
### Health Check
```python
from auth.tokens.monitoring import TokenMonitoring
async def auth_health_check() -> dict:
"""Health check системы аутентификации"""
monitoring = TokenMonitoring()
try:
# Проверяем состояние системы токенов
health = await monitoring.health_check()
# Получаем статистику
stats = await monitoring.get_token_statistics()
return {
"status": health.get("status", "unknown"),
"redis_connected": health.get("redis_connected", False),
"active_sessions": stats.get("session_tokens", 0),
"oauth_tokens": stats.get("oauth_access_tokens", 0) + stats.get("oauth_refresh_tokens", 0),
"memory_usage_mb": stats.get("memory_usage", 0) / 1024 / 1024,
"timestamp": int(time.time())
}
except Exception as e:
return {
"status": "error",
"error": str(e),
"timestamp": int(time.time())
}
# Использование в endpoint
async def health_endpoint():
"""Endpoint для мониторинга"""
health_data = await auth_health_check()
if health_data["status"] == "healthy":
return {"health": health_data, "status": 200}
else:
return {"health": health_data, "status": 503}
```
### Статистика использования
```python
async def get_auth_statistics() -> dict:
"""Получить статистику использования аутентификации"""
monitoring = TokenMonitoring()
stats = await monitoring.get_token_statistics()
return {
"sessions": {
"active": stats.get("session_tokens", 0),
"total_memory": stats.get("memory_usage", 0)
},
"oauth": {
"access_tokens": stats.get("oauth_access_tokens", 0),
"refresh_tokens": stats.get("oauth_refresh_tokens", 0)
},
"verification": {
"pending": stats.get("verification_tokens", 0)
},
"redis": {
"connected": stats.get("redis_connected", False),
"memory_usage_mb": stats.get("memory_usage", 0) / 1024 / 1024
}
}
```
## 🔒 Безопасность для микросервисов
### Валидация токенов
```python
async def secure_token_validation(token: str) -> dict:
"""Безопасная валидация токена с дополнительными проверками"""
if not token or len(token) < 10:
return {"valid": False, "error": "Invalid token format"}
try:
sessions = SessionTokenManager()
# 1. Проверяем JWT структуру и подпись
payload = await sessions.verify_session(token)
if not payload:
return {"valid": False, "error": "Invalid JWT token"}
user_id = payload.get("user_id")
if not user_id:
return {"valid": False, "error": "Missing user_id in token"}
# 2. Проверяем существование сессии в Redis
session_exists = await redis.exists(f"session:{user_id}:{token}")
if not session_exists:
return {"valid": False, "error": "Session not found in Redis"}
# 3. Проверяем TTL
ttl = await redis.ttl(f"session:{user_id}:{token}")
if ttl <= 0:
return {"valid": False, "error": "Session expired"}
# 4. Обновляем last_activity
await redis.hset(f"session:{user_id}:{token}", "last_activity", int(time.time()))
return {
"valid": True,
"user_id": user_id,
"username": payload.get("username"),
"expires_in": ttl,
"last_activity": int(time.time())
}
except Exception as e:
return {"valid": False, "error": f"Validation error: {str(e)}"}
```
### Rate Limiting
```python
from collections import defaultdict
import time
# Простой in-memory rate limiter (для production используйте Redis)
request_counts = defaultdict(list)
async def rate_limit_check(user_id: str, max_requests: int = 100, window_seconds: int = 60) -> bool:
"""Проверка rate limiting для пользователя"""
current_time = time.time()
user_requests = request_counts[user_id]
# Удаляем старые запросы
user_requests[:] = [req_time for req_time in user_requests if current_time - req_time < window_seconds]
# Проверяем лимит
if len(user_requests) >= max_requests:
return False
# Добавляем текущий запрос
user_requests.append(current_time)
return True
# Использование в middleware
async def auth_with_rate_limiting(request):
"""Аутентификация с rate limiting"""
auth_result = await authenticate_request(request)
if auth_result["authenticated"]:
user_id = str(auth_result["user_id"])
if not await rate_limit_check(user_id):
return {"error": "Rate limit exceeded", "status": 429}
return auth_result
```
## 🧪 Тестирование интеграции
### Unit тесты
```python
import pytest
from unittest.mock import AsyncMock, patch
@pytest.mark.asyncio
async def test_microservice_auth():
"""Тест аутентификации в микросервисе"""
# Mock request с токеном
mock_request = AsyncMock()
mock_request.headers = {"authorization": "Bearer valid_token"}
# Mock SessionTokenManager
with patch('auth.tokens.sessions.SessionTokenManager') as mock_sessions:
mock_sessions.return_value.verify_session.return_value = {
"user_id": "123",
"username": "testuser",
"exp": int(time.time()) + 3600
}
result = await authenticate_request(mock_request)
assert result["authenticated"] is True
assert result["user_id"] == "123"
assert result["username"] == "testuser"
@pytest.mark.asyncio
async def test_batch_token_validation():
"""Тест массовой валидации токенов"""
tokens = ["token1", "token2", "token3"]
with patch('auth.tokens.batch.BatchTokenOperations') as mock_batch:
mock_batch.return_value.batch_validate_tokens.return_value = {
"token1": True,
"token2": False,
"token3": True
}
results = await validate_multiple_tokens(tokens)
assert results["token1"] is True
assert results["token2"] is False
assert results["token3"] is True
```
### Integration тесты
```python
@pytest.mark.asyncio
async def test_redis_integration():
"""Тест интеграции с Redis"""
from storage.redis import redis
# Тестируем подключение
ping_result = await redis.ping()
assert ping_result is True
# Тестируем операции с сессиями
test_key = "session:test:token123"
test_data = {"user_id": "123", "username": "testuser"}
# Сохраняем данные
await redis.hset(test_key, mapping=test_data)
await redis.expire(test_key, 3600)
# Проверяем данные
stored_data = await redis.hgetall(test_key)
assert stored_data[b"user_id"].decode() == "123"
assert stored_data[b"username"].decode() == "testuser"
# Проверяем TTL
ttl = await redis.ttl(test_key)
assert ttl > 0
# Очищаем
await redis.delete(test_key)
```
## 📋 Checklist для интеграции
### Подготовка
- [ ] Настроен Redis connection pool с теми же параметрами
- [ ] Установлены зависимости: `auth.tokens.*`, `auth.utils`
- [ ] Настроены environment variables (JWT_SECRET_KEY, REDIS_URL)
### Реализация
- [ ] Реализована функция извлечения токенов из запросов
- [ ] Добавлена проверка сессий через SessionTokenManager
- [ ] Настроена обработка ошибок аутентификации
- [ ] Добавлен health check endpoint
### Безопасность
- [ ] Валидация токенов включает проверку Redis сессий
- [ ] Настроен rate limiting (опционально)
- [ ] Логирование событий аутентификации
- [ ] Обработка истекших токенов
### Мониторинг
- [ ] Health check интегрирован в систему мониторинга
- [ ] Метрики аутентификации собираются
- [ ] Алерты настроены для проблем с Redis/JWT
### Тестирование
- [ ] Unit тесты для функций аутентификации
- [ ] Integration тесты с Redis
- [ ] E2E тесты с реальными токенами
- [ ] Load тесты для проверки производительности