【问题标题】:Django and Middleware which uses request.user is always Anonymous使用 request.user 的 Django 和中间件始终是匿名的
【发布时间】:2014-12-02 03:57:31
【问题描述】:

我正在尝试制作基于子域等为用户更改某些字段的中间件...

唯一的问题是 request.user 在中间件中总是以 AnonymousUser 的形式出现,但在视图中却是正确的用户。我在设置中保留了 django 使用的默认身份验证和会话中间件。

这里有一个类似的问题:Django, request.user is always Anonymous 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

【问题讨论】:

  • 中间件的顺序很重要。确保身份验证中间件在设置中列在您的前面
  • 我已经将它包含在中间件的末尾,在 django 运行了它的默认值之后。 (Auth 和 Session 的)
  • 这里一定有其他事情发生,一些你没有发布的额外代码。
  • 嘿,是的,对不起,我在 process_request 和 process_view 之间切换。现在更新了。
  • django 版本?你怎么做授权?也发布 MIDDLEWARE_CLASSES 设置。

标签: python django django-rest-framework


【解决方案1】:

我已经通过从请求中获取 DRF 令牌并将 request.user 加载到与该模型关联的用户来解决了这个问题。

我有默认的 django 身份验证和会话中间件,但似乎 DRF 在中间件之后使用它的令牌身份验证来解析用户(所有请求都是 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。

希望这可以帮助将来的人。

【讨论】:

  • 我也遇到了同样的情况,而且效果很好!不过,我想知道,如果这不会导致在中间件之后运行的任何东西对令牌进行一秒钟的处理以验证令牌?那不好吗?我对 DRF 太陌生了,不知道哪种方式。
  • 嘿,所以我发现这不是实现它的最佳方式。实现它的正确方法是将 rest_frameworks 身份验证中间件包含在设置中的 AUTHENTICATION_BACKENDS 中。 'rest_framework.authentication.TokenAuthentication'
  • @Jordan 你能详细说明你的最后评论吗?我有同样的问题,并尝试将 TokenAuthentication 添加到 AUTHENTICATION_BACKENDS,但这并没有解决我的问题。将它添加到 AUTHENTICATION_BACKENDS 对您有何帮助?谢谢
  • @baselq 我实际上又回到了这一点,因为无效令牌只是被拒绝而不是在登录时删除它们,然后我们不得不告诉用户删除他们的 cookie 才能登录。
  • @Jordan 请看我上面的回答,这似乎与问题无关。
【解决方案2】:

今天遇到了同样的问题。

TL;DR;

代码示例跳过下面


说明

事情是DRF有自己的事情流程,就在django请求life-cycle的中间。

所以如果正常的中间件流程是:

  1. request_middleware(在开始处理请求之前)
  2. view_middleware(调用视图之前)
  3. template_middleware(渲染前)
  4. response_middleware(最终响应之前)

DRF 代码,覆盖默认的 django 视图代码,并执行their own code

在上面的链接中,您可以看到他们用自己的方法包装了原始请求,其中一种方法是 DRF 身份验证。

回到你的问题,这就是在中间件中使用request.user 为时过早的原因,因为它只有在 view_middleware** 执行之后才获得它的值。

我采用的解决方案是让我的中间件设置为LazyObject。 这很有帮助,因为我的代码(实际的 DRF ApiVIew)在实际用户已由 DRF's authentication 设置时执行。 这个解决方案是proposed here一起讨论的。

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

【讨论】:

  • 我收到object() takes no parameters
  • 你用的是什么 django?我的解决方案适用于 1.9。新的 1.10 版本在中间件方面有很多重大变化。看看这个docs.djangoproject.com/en/1.10/releases/1.10/…,如果你有任何结论,请发布(:一旦我移动到 1.10,我也会更新我的原始答案..
  • 这是一个非常巧妙的解决方案!我将发布 Django 1.11 的更新答案
  • 生活更安全。优雅的解决方案
【解决方案3】:

基于上面 Daniel Dubovski 非常优雅的解决方案,这里有一个 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 在其基础 view class 中所做的工作。至少对于我需要的东西,在这里添加更有意义。

    基本上,我只是从 DRF 的代码中覆盖方法“perform_authentication()”,因为我需要在请求中添加更多与当前用户相关的内容。该方法最初调用'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'
    

    之后,在您自己的视图中,无需将 DRF 中的 APIView 设置为父类,只需将该类设置为父类。

    【讨论】:

      【解决方案5】:

      Daniel Dubovski's solution 在大多数情况下可能是最好的。

      惰性对象方法的问题在于您是否需要依赖副作用。就我而言,无论如何,我都需要为每个请求发生一些事情。

      如果我要使用像request.custom_prop 这样的特殊值,则必须针对每个请求对其进行评估,以使副作用发生。我注意到other people are setting 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】:

        accepted answer 只考虑TokenAuthentication - 在我的例子中,配置了更多的身份验证方法。因此,我直接初始化了 DRF 的 Request,它调用 DRF 的身份验证机制并循环通过所有配置的身份验证方法。

        不幸的是,由于必须查询Token 对象,它仍然会在数据库上引入额外的负载(接受的答案也有这个问题)。在this answer 中使用SimpleLazyObject 的技巧是一个更好的解决方案,但它不适用于我的用例,因为我需要直接在中间件中使用用户信息 - 我正在扩展django_prometheus 中的指标并处理调用get_response 之前的请求。

        from rest_framework.request import Request as RestFrameworkRequest
        from rest_framework.views import APIView
        
        class MyMiddleware:
            def __init__(self, get_response):
                self.get_response = get_response
        
            def __call__(self, request):
                drf_request: RestFrameworkRequest = APIView().initialize_request(request)
                user = drf_request.user
                ...
                return self.get_response(request)
        

        【讨论】:

          【解决方案7】:

          我对那里的解决方案不太满意。这是一个解决方案,它使用一些 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'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.
          

          请注意,这并不能避免必须进行两次身份验证。之后,DRF 仍然会很高兴地进行身份验证。

          【讨论】:

          • 这看起来是一个很好的解决方案,它是否会导致任何副作用,因为它正在中间件内部初始化“DRF 请求”?安全吗?
          【解决方案8】:

          我遇到了同样的问题并决定更改我的设计。我没有使用中间件,而是简单地修补 rest_framework.views.APIView

          在我的情况下,我需要修补 check_permissions,但你可以修补任何适合你的问题。看看the source code

          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 *
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2020-07-16
            • 2019-06-07
            • 2017-01-03
            • 2014-09-15
            • 2010-10-08
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多