Подтвердить что ты не робот

Django и Middleware, которые используют request.user, всегда являются анонимными

Я пытаюсь создать промежуточное программное обеспечение, которое изменяет некоторые поля для пользователя в зависимости от субдомена и т. Д...

Единственная проблема заключается в том, что request.user всегда входит в состав промежуточного ПО как AnonymousUser, но затем является правильным пользователем в представлениях. Я оставил стандартную аутентификацию и сессионное промежуточное ПО, которое использует django в настройках.

Здесь есть похожий вопрос: Django, request.user всегда является анонимным пользователем Но не слишком часто отвечаю на общий вопрос, потому что я не использую разные методы аутентификации, и аутентификация djangos выполняется до того, как я запускаю свое собственное промежуточное ПО.

Есть ли способ при использовании DRF получить request.user в промежуточном программном обеспечении? Я покажу пример кода здесь:

class SampleMiddleware(object):

  def process_view(self, request, view_func, view_args, view_kwargs):
    #This will be AnonymousUser.  I need it to be the actual user making the request.
    print (request.user)    

  def process_response(self, request, response):
    return response

с process_request:

class SampleMiddleware(object):

  def process_request(self, request):
    #This will be AnonymousUser.  I need it to be the actual user making the request.
    print (request.user)    

  def process_response(self, request, response):
    return response
4b9b3361

Ответ 1

Привет, ребята, я решил эту проблему, получив токен DRF от запросов и загрузив request.user пользователю, связанному с этой моделью.

У меня была стандартная проверка подлинности django и промежуточное программное обеспечение для сеанса, но, похоже, DRF использовал его токен auth после промежуточного программного обеспечения для разрешения пользователя (все запросы были запросами CORS, возможно, именно поэтому). Здесь мой обновленный класс промежуточного ПО:

from re import sub
from rest_framework.authtoken.models import Token
from core.models import OrganizationRole, Organization, User

class OrganizationMiddleware(object):

  def process_view(self, request, view_func, view_args, view_kwargs):
    header_token = request.META.get('HTTP_AUTHORIZATION', None)
    if header_token is not None:
      try:
        token = sub('Token ', '', request.META.get('HTTP_AUTHORIZATION', None))
        token_obj = Token.objects.get(key = token)
        request.user = token_obj.user
      except Token.DoesNotExist:
        pass
    #This is now the correct user
    print (request.user)

Это также можно использовать в process_view или process_request.

Надеюсь, это может помочь кому-то в будущем.

Ответ 2

Пришел к этому сегодня, имея ту же проблему.

TL; DR;

Пропустить ниже для примера кода


Объяснение

Thing - это DRF, свой собственный поток вещей, прямо в середине запроса django жизненного цикла.

Итак, если нормальный поток промежуточного ПО:

  • request_middleware (перед началом работы над запросом)
  • view_middleware (перед вызовом представления)
  • template_middleware (перед рендерингом)
  • response_middleware (до окончательного ответа)

Код DRF, переопределяет стандартный код представления django и выполняет собственный код.

В приведенной выше ссылке вы можете увидеть, что они обертывают исходный запрос своими методами, причем одним из этих методов является аутентификация DRF.

Итак, вернемся к вашему вопросу, поэтому использование request.user в промежуточном программном обеспечении преждевременно, поскольку оно получает только значение после view_middleware **.

В решении, с которым я работал, установлено мое промежуточное ПО LazyObject. Это помогает, потому что мой код (фактический DRF ApiVIew) выполняется, когда фактический пользователь уже установлен DRF-аутентификация. Это решение было предложенное здесь вместе с обсуждением.

Возможно, было лучше, если бы у DRF был лучший способ расширить их функциональность, но, как ни странно, это кажется лучше, чем предоставленное решение (как производительность, так и читаемость).


Пример кода

from django.utils.functional import SimpleLazyObject

def get_actual_value(request):
    if request.user is None:
        return None

    return request.user #here should have value, so any code using request.user will work


class MyCustomMiddleware(object):
    def process_request(self, request):
        request.custom_prop = SimpleLazyObject(lambda: get_actual_value(request))

Ответ 3

Основываясь на Даниэле Дубовском, очень элегантном решении выше, вот пример промежуточного программного обеспечения для Django 1.11:

from django.utils.functional import SimpleLazyObject
from organization.models import OrganizationMember
from django.core.exceptions import ObjectDoesNotExist


def get_active_member(request):
    try:
        active_member = OrganizationMember.objects.get(user=request.user)
    except (ObjectDoesNotExist, TypeError):
        active_member = None
    return active_member


class OrganizationMiddleware(object):
    def __init__(self, get_response):
        self.get_response = get_response


    def __call__(self, request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.

        request.active_member = SimpleLazyObject(lambda: get_active_member(request))

        response = self.get_response(request)

        # Code to be executed for each request/response after
        # the view is called.
        return response

Ответ 4

Я знаю, что это не совсем ответ на вопрос "можем ли мы получить доступ к этому из промежуточного программного обеспечения", но я думаю, что это более элегантное решение VS, выполняющее ту же работу в промежуточном программном обеспечении VS, что и DRJ в своем базовом классе представления. По крайней мере, для того, что мне было нужно, было бы больше смысла добавить сюда.

По сути, я просто переопределяю метод 'execute_authentication()' из кода DRF, поскольку мне нужно было добавить в запрос больше вещей, связанных с текущим пользователем. Метод только что первоначально вызвал 'request.user'.

class MyGenericViewset(viewsets.GenericViewSet):

    def perform_authentication(self, request):
        request.user

        if request.user and request.user.is_authenticated():
            request.my_param1 = 'whatever'

После этого в ваших собственных представлениях вместо настроек APIView из DRF в качестве родительского класса просто установите этот класс в качестве родительского.

Ответ 5

Решение Даниэля Дубовского, вероятно, является лучшим в большинстве случаев.

Проблема с ленивым объектным подходом заключается в том, что вам нужно полагаться на побочные эффекты. В моем случае мне нужно, чтобы что-то происходило для каждого запроса, несмотря ни на что.

Если бы я использовал специальное значение, например request.custom_prop, оно должно оцениваться для каждого запроса на наличие побочных эффектов. Я заметил, что другие люди устанавливают request.user, но он не работает для меня, так как некоторые промежуточные программы или класс аутентификации перезаписывают это свойство.

Что если DRF поддерживает собственное промежуточное ПО? Где я могу подключить его? В моем случае самый простой способ (мне не нужен доступ к объекту request, только аутентифицированному пользователю) - подключиться к самому классу аутентификации:

from rest_framework.authentication import TokenAuthentication

class TokenAuthenticationWithSideffects(TokenAuthentication):

    def authenticate(self, request):
        user_auth_tuple = super().authenticate(request)

        if user_auth_tuple is None:
            return
        (user, token) = user_auth_tuple

        # Do stuff with the user here!

        return (user, token)

Тогда я мог бы просто заменить эту строку в моих настройках:

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": (
        #"rest_framework.authentication.TokenAuthentication",
        "my_project.authentication.TokenAuthenticationWithSideffects",
    ),
    # ...
}

Я не продвигаю это решение, но, возможно, оно поможет кому-то еще.

Плюсы:

  • Это решает эту конкретную проблему
  • Там нет двойной аутентификации
  • Простота в обслуживании

Минусы:

  • Не проверено в производстве
  • Вещи случаются в неожиданном месте
  • Побочные эффекты...

Ответ 6

Я был не совсем доволен решениями там. Здесь решение, которое использует некоторые внутренние компоненты DRF, чтобы убедиться, что в промежуточном программном обеспечении применяется правильная аутентификация, даже если представление имеет определенные классы разрешений. Он использует промежуточный обработчик process_view который дает нам доступ к представлению, которое мы собираемся поразить:

class CustomTenantMiddleware():
    def process_view(self, request, view_func, view_args, view_kwargs):
        # DRF saves the class of the view function as the .cls property
        view_class = view_func.cls
        try:
            # We need to instantiate the class
            view = view_class()
            # And give it an action_map. It not relevant for us, but otherwise it errors.
            view.action_map = {}
            # Here our fully formed and authenticated (or not, depending on credentials) request
            request = view.initialize_request(request)
        except (AttributeError, TypeError):
            # Can't initialize the request from this view. Fallback to using default permission classes
            request = APIView().initialize_request(request)

        # Here the request is fully formed, with the correct permissions depending on the view.

Обратите внимание, что это не избавляет от необходимости проходить аутентификацию дважды. DRF все равно с радостью выполнит аутентификацию сразу после этого.

Ответ 7

У меня была такая же проблема, и я решил изменить свой дизайн. Вместо того, чтобы использовать Middleware, я просто исправляю патч rest_framework.views.APIView.

В моем случае мне нужно было патчить check_permissions, но вы можете патчить то, что соответствует вашей проблеме. Посмотрите исходный код.

settings.py

INSTALLED_APPS = [
    ..
    'myapp',
]

MyApp/patching.py

import sys

from rest_framework.views import APIView as OriginalAPIView


class PatchedAPIView(OriginalAPIView):
    def check_permissions(self, request):
        print(f"We should do something with user {request.user}"
        return OriginalAPIView.check_permissions(self, request)


# We replace the Django REST view with our patched one
sys.modules['rest_framework'].views.APIView = PatchedAPIView

MyApp/__ init__.py

from .patching import *