简体   繁体   English

使用 request.user 的 Django 和中间件始终是匿名的

[英]Django and Middleware which uses request.user is always Anonymous

I'm trying to make middleware which alters some fields for the user based on subdomain, etc...我正在尝试制作基于子域等为用户更改某些字段的中间件...

The only problem is the request.user always comes in as AnonymousUser within the middleware, but is then the correct user within the views.唯一的问题是 request.user 在中间件中总是作为 AnonymousUser 出现,但在视图中是正确的用户。 I've left the default authentication and session middleware django uses within the settings.我在设置中保留了 django 使用的默认身份验证和会话中间件。

There is a similar question here: Django, request.user is always Anonymous User But doesn't overly answer the total question because I'm not using different authentication methods, and djangos authentication is running before I invoke my own middleware.这里有一个类似的问题: Django, request.user is always Anonymous User但并没有过度回答整个问题,因为我没有使用不同的身份验证方法,并且在调用我自己的中间件之前运行 djangos 身份验证。

Is there a way, while using DRF, to get the request.user within the middleware?在使用 DRF 时,有没有办法在中间件中获取 request.user? I'll show some sample code here:我将在这里展示一些示例代码:

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

with process_request:使用 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

I've solved this problem by getting DRF token from the requests and loading request.user to the user associated to that model.我通过从请求中获取 DRF 令牌并将 request.user 加载到与该模型关联的用户来解决这个问题。

I had the default django authentication and session middleware, but it seems DRF was using it's token auth after middleware to resolve the user (All requests were CORS requests, this might have been why).我有默认的 django 身份验证和会话中间件,但似乎 DRF 在中间件之后使用它的令牌身份验证来解析用户(所有请求都是 CORS 请求,这可能是原因)。 Here's my updated middleware class:这是我更新的中间件类:

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)

This can be used on process_view or process_request as well.这也可以用于 process_view 或 process_request。

Hopefully this can help someone out in the future.希望这可以在将来帮助某人。

Came across this today while having the same problem.今天遇到了这个问题,同时遇到了同样的问题。

TL;DR; TL; 博士;

Skip below for code example跳过下面的代码示例


Explanation解释

Thing is DRF have their own flow of things, right in the middle of the django request life-cycle .事情是 DRF 有自己的事情流,就在 django 请求 生命周期的中间。

So if the normal middleware flow is :所以如果正常的中间件流程是:

  1. request_middleware (before starting to work on the request) request_middleware(在开始处理请求之前)
  2. view_middleware (before calling the view) view_middleware(在调用视图之前)
  3. template_middleware (before render) template_middleware(渲染前)
  4. response_middleware (before final response) response_middleware(在最终响应之前)

DRF code, overrides the default django view code, and executes their own code . DRF 代码,覆盖默认的 django 视图代码,并执行自己的代码

In the above link, you can see that they wrap the original request with their own methods, where one of those methods is DRF authentication.在上面的链接中,您可以看到他们使用自己的方法包装原始请求,其中一种方法是 DRF 身份验证。

So back to your question, this is the reason using request.user in a middleware is premature, as it only gets it's value after view_middleware** executes.所以回到你的问题,这就是在中间件中使用request.user为时过早的原因,因为它只有view_middleware** 执行后才获得它的价值。

The solution I went with, is having my middleware set a LazyObject .我采用的解决方案是让我的中间件设置一个LazyObject This helps, because my code (the actual DRF ApiVIew) executes when the actual user is already set by DRF's authentication .这有帮助,因为我的代码(实际的 DRF ApiVIew)在实际用户已经由DRF 的 authentication设置时执行。 This solution was proposed here together with a discussion.此解决方案是在此处与讨论一起提出的

Might have been better if DRF had a better way to extend their functionality, but as things are, this seems better than the provided solution (both performance and readability wise).如果 DRF 有更好的方法来扩展其功能可能会更好,但事实上,这似乎比提供的解决方案更好(性能和可读性方面)。


Code Example代码示例

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

Based on Daniel Dubovski's very elegant solution above, here's an example of middleware for Django 1.11:基于上面 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

I know it's not exactly answering the 'can we access that from the middleware' question, but I think it's a more elegant solution VS doing the same work in the middleware VS what DRJ does in its base view class .我知道它并没有完全回答“我们可以从中间件访问它吗”的问题,但我认为这是一个更优雅的解决方案 VS 在中间件中执行相同的工作 VS DRJ 在其基本视图类中所做的工作。 At least for what I needed, it made more sense to add here.至少对于我所需要的,在这里添加更有意义。

Basically, I'm just overriding the method 'perform_authentication()' from DRF's code, since I needed to add more things related to the current user in the request.基本上,我只是覆盖了 DRF 代码中的“perform_authentication()”方法,因为我需要在请求中添加更多与当前用户相关的内容。 The method just originally call 'request.user'.该方法最初只是调用“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'

After that in your own views, instead of settings APIView from DRF as a parent class, simply set that class as a parent.之后在您自己的视图中,而不是将 DRF 中的 APIView 设置为父类,只需将该类设置为父类。

Daniel Dubovski's solution is probably the best in most cases.在大多数情况下, Daniel Dubovski 的解决方案可能是最好的。

The problem with the lazy object approach is if you need to rely on the side effects.惰性对象方法的问题在于您是否需要依赖副作用。 In my case, I need something to happen for each request, no matter what.就我而言,无论如何,我都需要为每个请求发生一些事情。

If I'd use a special value like request.custom_prop , it has to be evaluated for each request for the side effects to happen.如果我使用request.custom_prop类的特殊值,则必须针对每个请求对其进行评估,以便发生副作用。 I noticed that other people are setting request.user , but it doesn't work for me since some middleware or authentication class overwrites this property.我注意到其他人正在设置request.user ,但它对我不起作用,因为某些中间件或身份验证类会覆盖此属性。

What if DRF supported its own middleware?如果 DRF 支持自己的中间件会怎样? Where could I plug it in?我可以在哪里插入它? The easiest way in my case (I don't need to access the request object, only the authenticated user) seems to be to hook into the authentication class itself:在我的情况下(我不需要访问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)

Then I could just replace this line in my settings:然后我可以在我的设置中替换这一行:

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

I'm not promoting this solution, but maybe it will help someone else.我不是在推广这个解决方案,但也许它会帮助别人。

Pros:优点:

  • It to solves this specific problem它解决了这个特定的问题
  • There's no double authentication没有双重身份验证
  • Easy to maintain易于维护

Cons:缺点:

  • Not tested in production未在生产中测试
  • Things happen in an unexpected place事情发生在意想不到的地方
  • Side effects...副作用...

I wasn't quite happy with the solutions out there.我对那里的解决方案不太满意。 Here's a solution that uses some DRF internals to make sure that the correct authentication is applied in the middleware, even if the view has specific permissions classes.这是一个解决方案,它使用一些 DRF 内部机制来确保在中间件中应用正确的身份验证,即使视图具有特定的权限类。 It uses the middleware hook process_view which gives us access to the view we're about to hit:它使用中间件钩子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.

Note that this doesn't avoid having to authenticate twice.请注意,这并不能避免必须进行两次身份验证。 DRF will still happily authenticate right afterwards.之后 DRF 仍然会很高兴地进行身份验证。

I had the same issue and decided to change my design.我有同样的问题,并决定改变我的设计。 Instead of using a Middleware I simply monkey-patch rest_framework.views.APIView .我没有使用中间件,而是简单地修补了rest_framework.views.APIView

In my case I needed to patch check_permissions but you can patch whatever fits your problem.在我的情况下,我需要修补check_permissions但你可以修补任何适合你的问题。 Have a look at the the source code .看看源代码

settings.py设置.py

INSTALLED_APPS = [
    ..
    'myapp',
]

myapp/patching.py我的应用程序/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 myapp/__init__.py

from .patching import *

The accepted answer only takes TokenAuthentication in consideration - in my case, there are more authentication methods configured.接受的答案仅考虑TokenAuthentication - 就我而言,配置了更多身份验证方法。 I thus went with initializing the DRF's Request directly which invokes DRF's authentication machinery and loops through all configured authentication methods.因此,我直接初始化 DRF 的Request ,它调用 DRF 的身份验证机制并循环所有配置的身份验证方法。

Unfortunately, it still introduces an additional load on the database since the Token object must be queried (the accepted answer has this problem as well).不幸的是,它仍然给数据库带来了额外的负载,因为必须查询Token对象(接受的答案也有这个问题)。 The trick with SimpleLazyObject in this answer is a much better solution, but it didn't work for my use case because I need the user info in the middleware directly - I'm extending the metrics in django_prometheus and it processes the request before get_response is called.这个答案中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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM