python 使用request的Django和中间件,用户总是匿名的

hi3rlvi2  于 2022-12-28  发布在  Python
关注(0)|答案(8)|浏览(146)

我正在尝试做一个中间件,它可以根据子域等为用户更改一些字段。
唯一的问题是请求。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

使用进程请求:

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
fwzugrvs

fwzugrvs1#

我已经解决了这个问题,方法是从请求中获取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 ', '', header_token)
        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上。
希望这能在未来帮助到一些人。

e3bfsja2

e3bfsja22#

今天遇到这个,同时也有同样的问题。

    • TL; DR;**

跳过下面的代码示例

    • 说明**

问题是DRF有自己的东西流,就在django请求life-cycle的中间。
因此,如果正常的中间件流程是:

  1. request_middleware(在开始处理请求之前)
  2. view_middleware(调用视图之前)
    1.模板_中间件(渲染前)
  3. response_middleware(最终响应之前)
    DRF代码,覆盖默认的django视图代码,并执行自己的代码。
    在上面的链接中,您可以看到它们用自己的方法 Package 原始请求,其中一个方法是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))
jdgnovmf

jdgnovmf3#

accepted answer只考虑TokenAuthentication-在我的例子中,配置了更多的认证方法,因此我直接初始化DRF的Request,调用DRF的认证机制并循环所有配置的认证方法。
不幸的是,由于必须查询Token对象,因此它仍然会给数据库带来额外的负载(公认的答案也有这个问题)。在this answer中使用SimpleLazyObject的技巧是一个好得多的解决方案,但它不适合我的用例,因为我需要中间件中的用户信息-我M扩展X1 E2 F1 X中的度量,并且它在调用X1 M5 N1 X之前处理该请求。

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

oogrdqng4#

基于上面丹尼尔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
6psbrbz9

6psbrbz95#

Daniel Dubovski's solution在大多数情况下可能是最好的。
惰性对象方法的问题在于是否需要依赖副作用。在我的例子中,我需要对每个请求都发生一些事情,无论什么。
如果我使用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",
    ),
    # ...
}

我不是在推广这个解决方案,但也许它会帮助别人。

    • 优点:**
  • 它解决了这个具体问题
  • 没有双重认证
  • 易于维护
    • 缺点:**
  • 未在生产中测试
  • 事情发生在意想不到的地方
  • 副作用
d6kp6zgx

d6kp6zgx6#

我知道这并没有完全回答“我们能从中间件访问它吗”的问题,但我认为这是一个更优雅的解决方案,而不是在中间件中做同样的工作,而不是DRJ在其基视图类中做同样的工作。
基本上,我只是覆盖了DRF代码中的方法'perform_authentication()',因为我需要在请求中添加更多与当前用户相关的内容。

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设置为父类,只需将该类设置为父类即可。

ckocjqey

ckocjqey7#

我对现有的解决方案不是很满意。这里有一个解决方案,它使用一些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仍然会很乐意在认证后立即进行认证。

hmae6n7t

hmae6n7t8#

我也遇到了同样的问题,决定改变我的设计,我没有使用中间件,而是简单地用猴子补丁rest_framework.views.APIView
在我的例子中,我需要修补check_permissions,但你可以修补任何适合你的问题。看看the source code

    • 设置. 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
    • 我的应用程序/初始化__. py**
from .patching import *

相关问题