DjangoRestFramework/Restframework-jwt认证流程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010377372/article/details/83754359

问题来源

在使用DRF过程中需要添加通过APPID/APPSECRET的方式来登录系统。本来想通过middleware的方式在request中设置user字段从而可以在views中使用。但是通过调试发现,在调用views之前,request中的user还存在。但是等到views中执行时,user就变成了Anonymous用户,大致判断是在后期认证过程中,restframework框架设置了request的user字段。因此,梳理一下restframework-jwt是如何设置request中的user字段的。

代码流程

首先了解一下middleware和views的执行顺序大致如下:

由此可见,在middleware中设置的user字段会被views中的user字段所覆盖。所以会出现middleware中设置了user,但是使用时user又不见了。

简化流程

解决middleware中设置user字段会被restframework覆盖的方法代码如下:

class KwsAppidSecretAuthMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # One-time configuration and initialization.

    def __call__(self, request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.

        if your_condition:
            # 通过设置_user可以设置request中的user而不被restframework覆盖
            # 这段代码实现的原因因为在后面的认证过程中的判断了是否存在_user字段,如果
            # 存在这一字段,则不再进行auth认证。所以说设置时也要确保是你确实在用restframework框 
            # 架
            request._user = user 

        response = self.get_response(request)

        return response

详细流程

对于views中的任意views最终都是继承自 APIView,在APIView中查找 as_view()代码如下:

# /site-packages/rest_framework/views.py:APIView
class APIView(View):
    ...
    @classmethod
    def as_view(cls, **initkwargs):
        """
        Store the original class on the view function.

        This allows us to discover information about the view when we do URL
        reverse lookups.  Used for breadcrumb generation.
        """
        if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
            def force_evaluation():
                raise RuntimeError(
                    'Do not evaluate the `.queryset` attribute directly, '
                    'as the result will be cached and reused between requests. '
                    'Use `.all()` or call `.get_queryset()` instead.'
                )
            cls.queryset._fetch_all = force_evaluation

        view = super(APIView, cls).as_view(**initkwargs)
        view.cls = cls
        view.initkwargs = initkwargs

        # Note: session based authentication is explicitly CSRF validated,
        # all other authentication is CSRF exempt.
        return csrf_exempt(view)

上面的代码可见调用了django的View中的as_view()

Django中View的as_view()代码如下

# /site-packages/django/views/generic/base.py:View
class View(object):
    ...
    @classonlymethod
    def as_view(cls, **initkwargs):
        """
        Main entry point for a request-response process.
        """
        for key in initkwargs:
            if key in cls.http_method_names:
                raise TypeError("You tried to pass in the %s method name as a "
                                "keyword argument to %s(). Don't do that."
                                % (key, cls.__name__))
            if not hasattr(cls, key):
                raise TypeError("%s() received an invalid keyword %r. as_view "
                                "only accepts arguments that are already "
                                "attributes of the class." % (cls.__name__, key))

        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.request = request
            self.args = args
            self.kwargs = kwargs
            return self.dispatch(request, *args, **kwargs)
        view.view_class = cls
        view.view_initkwargs = initkwargs

        # take name and docstring from class
        update_wrapper(view, cls, updated=())

        # and possible attributes set by decorators
        # like csrf_exempt from dispatch
        update_wrapper(view, cls.dispatch, assigned=())
        return view

由上面代码可见,会调用类的dispatch方法,在APIView的dispatch方法中有如下代码:

    def dispatch(self, request, *args, **kwargs):
        """
        `.dispatch()` is pretty much the same as Django's regular dispatch,
        but with extra hooks for startup, finalize, and exception handling.
        """
        self.args = args
        self.kwargs = kwargs
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            self.initial(request, *args, **kwargs)

            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed

            response = handler(request, *args, **kwargs)

        except Exception as exc:
            response = self.handle_exception(exc)

        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

dispatch方法又调用了同一个类中的initialize_request方法,initialize_request实现如下:

    def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)

        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )

initialize_request之后,调用initial方法,代码实现如下:

    def initial(self, request, *args, **kwargs):
        """
        Runs anything that needs to occur prior to calling the method handler.
        """
        self.format_kwarg = self.get_format_suffix(**kwargs)

        # Perform content negotiation and store the accepted info on the request
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg

        # Determine the API version, if versioning is in use.
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

        # Ensure that the incoming request is permitted
        self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)

在initial中会调用 self.perform_authentication(request),具体实现如下:

    def perform_authentication(self, request):
        """
        Perform authentication on the incoming request.

        Note that if you override this and simply 'pass', then authentication
        will instead be performed lazily, the first time either
        `request.user` or `request.auth` is accessed.
        """
        request.user

此时需要注意的是,这个request是之前 initialize_request生成的request了,所以request的user是一个方法,实现如下

    @property
    def user(self):
        """
        Returns the user associated with the current request, as authenticated
        by the authentication classes provided to the request.
        """
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                self._authenticate()
        return self._user

如果request中没有_user字段,再之后便会调用 self._authenticate(),代码实现如下:

    def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        """
        for authenticator in self.authenticators:
            try:
                user_auth_tuple = authenticator.authenticate(self)
            except exceptions.APIException:
                self._not_authenticated()
                raise

            if user_auth_tuple is not None:
                self._authenticator = authenticator
                self.user, self.auth = user_auth_tuple
                return

        self._not_authenticated()

而这其中的 authenticator则是setting中的rest_framework_jwt.authentication.JSONWebTokenAuthentication,这样以来就可以通过jwt认证用户了,最终修改了request中的user。

 

题外话:get_authenticators代码实现如下:

    def get_authenticators(self):
        """
        Instantiates and returns the list of authenticators that this view can use.
        """
        return [auth() for auth in self.authentication_classes]

    # self.authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    # 这是jwt认证的第一步,所以我们需要把setting中的DEFAULT_AUTHENTICATION_CLASSES设置为
    # 'rest_framework_jwt.authentication.JSONWebTokenAuthentication' 

猜你喜欢

转载自blog.csdn.net/u010377372/article/details/83754359
今日推荐