Django y Middleware que usa request.user es siempre anónimo

Estoy tratando de hacer middleware que altera algunos campos para el usuario según el subdominio, etc.

El único problema es que request.user siempre viene como AnonymousUser dentro del middleware, pero es el usuario correcto dentro de las vistas. He dejado la autenticación predeterminada y el middleware de sesión que usa Django dentro de la configuración.

Aquí hay una pregunta similar: Django, request.user siempre es un usuario anónimo Pero no responde demasiado a la pregunta total porque no estoy usando diferentes métodos de autenticación, y la autenticación djangos se está ejecutando antes de invocar mi propio middleware.

¿Hay alguna forma, mientras se usa DRF, para obtener el request.user dentro del middleware? Voy a mostrar algunos ejemplos de código aquí:

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 

con 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 

Hola chicos, he resuelto este problema obteniendo el token DRF de las solicitudes y cargando request.user al usuario asociado a ese modelo.

Tenía la autenticación de django y el middleware de sesión predeterminados, pero parece que DRF estaba usando su autenticación de token después del middleware para resolver al usuario (todas las solicitudes eran solicitudes CORS, esto podría haber sido el motivo). Aquí está mi clase de middleware actualizada:

 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) 

Esto también se puede usar en process_view o process_request.

Esperemos que esto pueda ayudar a alguien en el futuro.

Encontré esto hoy con el mismo problema.

TL; DR;

Saltar a continuación para el ejemplo de código


Explicación

La cosa es que los DRF tienen su propio flujo de cosas, justo en el medio del ciclo de vida de las solicitudes de django.

Así que si el flujo normal de middleware es:

  1. request_middleware (antes de comenzar a trabajar en la solicitud)
  2. view_middleware (antes de llamar a la vista)
  3. template_middleware (antes del render)
  4. response_middleware (antes de la respuesta final)

El código DRF, reemplaza el código de vista de django predeterminado y ejecuta su propio código .

En el enlace anterior, puede ver que envuelven la solicitud original con sus propios métodos, donde uno de esos métodos es la autenticación DRF.

Así que volviendo a su pregunta, esta es la razón por la que el uso de request.user en un middleware es prematuro, ya que solo obtiene su valor después de que se ejecuta view_middleware **.

La solución con la que fui, es que mi middleware configure un LazyObject . Esto ayuda, porque mi código (el actual DRF ApiVIew) se ejecuta cuando el usuario real ya está configurado por la autenticación de DRF . Esta solución fue propuesta aquí junto con una discusión.

Podría haber sido mejor si DRF hubiera tenido una mejor manera de ampliar su funcionalidad, pero como están las cosas, esto parece mejor que la solución provista (tanto el rendimiento como la legibilidad).


Ejemplo de código

 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)) 

Basado en la elegante solución de Daniel Dubovski que se encuentra arriba, aquí hay un ejemplo de middleware para 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 

Sé que no es exactamente responder a la pregunta “¿podemos acceder a eso desde el middleware?”, Pero creo que es una solución más elegante que hacer el mismo trabajo en el middleware VS que DRJ en su clase de vista base. Al menos por lo que necesitaba, tenía más sentido agregar aquí.

Básicamente, solo estoy sobrescribiendo el método ‘perform_authentication ()’ del código DRF, ya que necesitaba agregar más cosas relacionadas con el usuario actual en la solicitud. El método simplemente llama originalmente ‘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' 

Después de eso, en sus propias vistas, en lugar de configurar APIView de DRF como clase principal, simplemente establezca esa clase como padre.

La solución de Daniel Dubovski es probablemente la mejor en la mayoría de los casos.

El problema con el enfoque del objeto perezoso es si necesita confiar en los efectos secundarios. En mi caso, necesito que suceda algo para cada solicitud, sin importar qué.

Si utilizara un valor especial como request.custom_prop , debe evaluarse para cada solicitud para que ocurran los efectos secundarios. Noté que otras personas están configurando request.user , pero no funciona para mí ya que algún middleware o clase de autenticación sobrescribe esta propiedad.

¿Qué pasaría si DRF soportara su propio middleware? ¿Dónde podría enchufarlo? La forma más fácil en mi caso (no necesito acceder al objeto de request , solo el usuario autenticado) parece ser engancharse en la propia clase de autenticación:

 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) 

Entonces podría simplemente reemplazar esta línea en mi configuración:

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

No estoy promoviendo esta solución, pero quizás ayude a alguien más.

Pros:

  • Resuelve este problema específico.
  • No hay doble autenticación
  • Facil de mantener

Contras:

  • No probado en producción.
  • Las cosas pasan en un lugar inesperado.
  • Efectos secundarios…

No estaba del todo contento con las soluciones que hay. Aquí hay una solución que usa algunos elementos internos de DRF para asegurarse de que se aplique la autenticación correcta en el middleware, incluso si la vista tiene clases de permisos específicos. Utiliza el gancho de middleware process_view que nos da acceso a la vista que estamos a punto de alcanzar:

 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's not relevant for us, but otherwise it errors. view.action_map = {} # Here's 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. 

Tenga en cuenta que esto no evita tener que autenticarse dos veces. DRF todavía se autenticará felizmente después.