Получения пользователя при работе через Websocket

Поговорим о реализации аутентификации в веб-сервисе, построенном с использованием Django Rest Framework (DRF). Мы столкнулись с вызовом при поддержке двух методов аутентификации: токены DRF и OpenID Connect (OIDC) из Keycloak. Пересматривать всю логику авторизации и внедрять ее с нуля было непрактично в свете принципов SOLID. В данном контексте мы посмотрим на сохранение логики проверки токенов в базовых классах и использование Middleware для поддержки аутентификации через Websocket.

 

Проблема

Существующий сервис, разработанный на основе DRF, предоставлял два метода аутентификации: токены DRF и OpenID Connect (OIDC) из Keycloak. Возможность пересмотра логики с нуля была исключена в связи с принципами SOLID. Наша задача заключалась в сохранении основных классов и добавлении поддержки нового метода аутентификации через Websocket.

Решение

Процесс реализации сводился к внедрению Middleware, который расширял текущую логику аутентификации в DRF. Давайте разберемся с этим пошагово.

Определение роутов

Начнем с определения роутов, которые нужно обернуть в созданный Middleware для обеспечения аутентификации через Websocket.

websocket_urlpatterns = TelebackAuthMiddlewareStack(
    URLRouter([
        re_path(r"^notifications$", consumers.NotificationsConsumer.as_asgi(), name="ws_notifications", ),
    ])
)

Создание Middleware

Новый уровень аутентификации был добавлен через создание Middleware.

class AsyncTokenAuthentication(TokenAuthentication):
    async def authenticate(self, request):
        return await sync_to_async(super().authenticate)(request)


class AsyncOIDCAuthentication(OIDCAuthentication):
    async def authenticate(self, request, **kwargs):
        return await sync_to_async(super().authenticate)(request)

def make_request_of_scope(scope):
    request = HttpRequest()

    headers = scope.get('headers', [])
    auth_key = b'authorization'

    for name, value in headers:
        if name == auth_key:
            request.META["HTTP_AUTHORIZATION"] = value
    return request


class MakeRequestMiddleware:
    def __init__(self, app):
        self.app = app

    async def __call__(self, scope, receive, send):
        scope['request'] = make_request_of_scope(scope)
        return await self.app(scope, receive, send)


class TelebackOIDCAuthMiddleware:
    def __init__(self, app):
        self.app = app

    async def __call__(self, scope, receive, send):
        try:
            user, _ = await AsyncOIDCAuthentication().authenticate(scope['request'])
            scope['user'] = user

        except (Exception,):
            pass

        return await self.app(scope, receive, send)


class TokenAuthMiddleware:
    def __init__(self, app):
        self.app = app

    async def __call__(self, scope, receive, send):
        try:
            user, _ = await AsyncTokenAuthentication().authenticate(scope['request'])
            scope['user'] = user
        except (Exception,):
            pass

        return await self.app(scope, receive, send)


def TelebackAuthMiddlewareStack(inner):
    return MakeRequestMiddleware(TokenAuthMiddleware(TelebackOIDCAuthMiddleware(inner)))

В нашем веб-сервисе, построенном на библиотеке Channels (https://channels.readthedocs.io/en/latest/) с использованием ASGI, нам потребовалось асинхронное выполнение методов аутентификации. Особое внимание уделяется асинхронному подходу при проверке токена через AsyncTokenAuthentication и AsyncOIDCAuthentication. В этом контексте рассмотрим оптимизацию функции make_request_of_scope, которая теперь создает HttpRequest асинхронно и добавляет заголовок для аутентификации. Мы также внедрили отдельный посредник MakeRequestMiddleware для повторного использования request. В заключении, аутентификацию вызываем через TelebackOIDCAuthMiddleware и TokenAuthMiddleware, сохраняя результат в scope для будущего использования.

 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.