Django and Middleware which uses request.user is always Anonymous

前端 未结 7 1193
天命终不由人
天命终不由人 2021-01-30 10:37

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 wit

相关标签:
7条回答
  • 2021-01-30 11:21

    Came across this today while having the same problem.

    TL;DR;

    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.

    So if the normal middleware flow is :

    1. request_middleware (before starting to work on the request)
    2. view_middleware (before calling the view)
    3. template_middleware (before render)
    4. response_middleware (before final response)

    DRF code, overrides the default django view code, and executes their own code.

    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.

    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.

    The solution I went with, is having my middleware set a LazyObject. This helps, because my code (the actual DRF ApiVIew) executes when the actual user is already set by DRF's 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).


    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))
    
    0 讨论(0)
  • 2021-01-30 11:30

    Daniel Dubovski's solution is probably the best in most cases.

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

    What if DRF supported its own middleware? 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:

    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...
    0 讨论(0)
  • 2021-01-30 11:31

    Based on Daniel Dubovski's very elegant solution above, here's an example of middleware for 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
    
    0 讨论(0)
  • 2021-01-30 11:36

    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. 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. The method just originally call '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.

    0 讨论(0)
  • 2021-01-30 11:37

    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. It uses the middleware hook process_view which gives us access to the view we're about to hit:

    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.

    0 讨论(0)
  • 2021-01-30 11:43

    I've solved this problem by getting DRF token from the requests and loading request.user to the user associated to that model.

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

    Hopefully this can help someone out in the future.

    0 讨论(0)
提交回复
热议问题