Как реализовать Middleware для аутентификации WebSocket-соединений?

«Как реализовать Middleware для аутентификации WebSocket-соединений?» — вопрос из категории Безопасность, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Middleware для аутентификации WebSocket-соединений проверяет токен или другие учетные данные при установке соединения, обеспечивая безопасность. Это критически важно для защиты данных и контроля доступа к функциональности, предоставляемой через WebSocket.

Пример реализации для FastAPI с WebSocket:

from fastapi import WebSocket, WebSocketDisconnect, APIRouter
from fastapi.security import OAuth2PasswordBearer
from typing import Optional

router = APIRouter()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") # Пример, токен может быть получен иначе

# Ваша функция для проверки токена
def verify_token(token: str) -> bool:
    # Здесь должна быть реальная логика проверки токена (например, JWT)
    # Возвращает True, если токен валиден, иначе False
    return token == "valid_token" # Заглушка для примера

async def websocket_auth_middleware(websocket: WebSocket) -> Optional[str]:
    """
    Middleware для аутентификации WebSocket-соединения.
    Ожидает токен в первом текстовом сообщении.
    """
    try:
        # Токен может передаваться в первом сообщении или как query-параметр URL
        token = await websocket.receive_text()
        if not verify_token(token):
            # Закрываем соединение с кодом 1008 (Policy Violation) и причиной
            await websocket.close(code=1008, reason="Invalid authentication token")
            raise WebSocketDisconnect("Authentication failed")
        return token
    except WebSocketDisconnect:
        # Соединение уже закрыто или ошибка при получении токена
        raise

@router.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    try:
        # Применяем middleware для аутентификации
        token = await websocket_auth_middleware(websocket)
        # Дальнейшая логика работы с аутентифицированным сокетом
        await websocket.send_text(f"Hello, authenticated user with token: {token}")
        while True:
            data = await websocket.receive_text()
            await websocket.send_text(f"Message text was: {data}")
    except WebSocketDisconnect:
        print("WebSocket disconnected, authentication failed or client closed connection.")
    except Exception as e:
        print(f"An error occurred: {e}")

Ключевые аспекты:

  • Передача токена: Токен обычно передается в первом сообщении после установки соединения, в заголовках (если поддерживается протоколом) или как query-параметр URL при подключении. Выбор метода зависит от клиента и сервера.
  • Обработка ошибок: Важно корректно закрывать соединение при ошибках аутентификации, используя соответствующие коды состояния WebSocket (например, 1008 для нарушения политики, 4000-4999 для пользовательских кодов). Это позволяет клиенту понять причину разрыва.
  • Масштабирование: Для распределенных систем и масштабирования аутентификации и обмена сообщениями часто используются брокеры сообщений, такие как Redis Pub/Sub, Kafka или RabbitMQ, которые могут интегрироваться с механизмом аутентификации.