Django-REST-framework 用户认证源码分析

Python
392
0
0
2022-11-25
标签   Django

REST 用户认证源码

在Django中,从URL调度器中过来的HTTPRequest会传递给disatch(),使用REST后也一样

# REST的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

代码第三行通过一个方法initialize_request()重新分装了原来从URL调度器传来的request对象,并且返回的也是一个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()返回的是一个Request对象

class Request(object):

    def __init__(self, request, parsers=None, authenticators=None,
                 negotiator=None, parser_context=None):
        pass
        self._request = request
        self.parsers = parsers or ()
        # ...

Request这个类使用"组合"将普通的httprequest分装在它的内部,除此之外还提供了用于身份验证的authenticators,用于解析请求内容的解析器(parsers)只关心authenticators

authenticators由self.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]

get_authenticators遍历authentication_classes,并实例化authentication_classes中的对象加入到列表中返回

authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES

实际上authentication_classes只是一个包含认证类的列表

已经乱了,整理一下

首先,用户会生成一个httprequest,这个请求到URL调度器后会执行as_view()

path('shop/', views.ShopView.as_view())

而在as_view()中就会把这个原生的httprequest传递给dispatch()dispatch()中会对这个httprequest进一步封装,在这里具体就是增加了一个authenticators,他是一个列表,列表中是一系列从authentication_classes列表中实例化出来的对象。

然后进入try块,执行self.initial(request, *args, **kwargs),这条语句用来 “运行在调用方法处理程序之前需要发生的任何事情” 可以说是一个功能集合,聚合了认证管理,权限管理,版本控制等几个功能模块

def initial(self, request, *args, **kwargs):

    self.format_kwarg = self.get_format_suffix(**kwargs)
    # 执行内容协商并存储关于请求的接受信息
    neg = self.perform_content_negotiation(request)
    request.accepted_renderer, request.accepted_media_type = neg

    # 版本控制
    version, scheme = self.determine_version(request, *args, **kwargs)
    request.version, request.versioning_scheme = version, scheme
    # 用户认证
    self.perform_authentication(request)
    # 权限控制
    self.check_permissions(request)
    # 访问频率控制
    self.check_throttles(request)

现在只关心用户认证的工作,进入perform_authentication(request)(现在的request已经是重新包装过的的request了),也只有一句话。

def perform_authentication(self, request):
    request.user

它调用了这个request对象的user属性,进入user,是一个属性方法,主体是调用了self._authenticate()

@property
def user(self):
    if not hasattr(self, '_user'):
        # 只是一个上下文管理器,方便清理之类的工作 
        with wrap_attributeerrors():
            self._authenticate()
    return self._user

现在是那个封装过的request对象调用了自己的user属性方法,所以self已经是request了,之前是在视图(view.py)中自己定义的ShopView

进入self._authenticate()

    def _authenticate(self):
       
        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()

他会遍历self.authenticators,现在的self是那个分装过的request,所以self.authenticators其实就是上面列表生成式生成的那个认证类对象列表,它遍历并调用每一个认证类对象的authenticate方法,这个方法必须覆盖,否则会抛出NotImplementedError异常

def authenticate(self, request):
    raise NotImplementedError(".authenticate() must be overridden.")

这里的逻辑是一旦authenticate()抛出exceptions.APIException异常,就调用self._not_authenticated()也就是认证失败,如果没有抛出异常,就进入下面的if语句,判断返回值是否是None如果是,本次循环就结束,也就是不使用这个认证类对象,转而使用下一个认证类对象,如果不为None则进行一个序列解包操作,把元组中的第一个元素赋值给self.user第二个元素赋值给self.auth,终止循环,如果遍历完整个self.authenticators还是没认证成功,就会执行最后一行的self._not_authenticated()和认证时抛出异常一样,认证失败。

def _not_authenticated(self):
    """
    设置authenticator,user&authToken表示未经过身份验证的请求。
    默认值为None,AnonymousUser&None。
    """
    self._authenticator = None

    if api_settings.UNAUTHENTICATED_USER:
        self.user = api_settings.UNAUTHENTICATED_USER()
    else:
        self.user = None

    if api_settings.UNAUTHENTICATED_TOKEN:
        self.auth = api_settings.UNAUTHENTICATED_TOKEN()
    else:
        self.auth = None

认证失败后的逻辑是:先看配置文件中有没有UNAUTHENTICATED_USER,如果有,就把这个配置内容作为默认的“匿名用户”,否则就把self.user赋值为None,self.auth也一样。

这大概就是认证的基本流程了。

过程总结

用户发出请求,产生request,传递到URL调度器,url调度器将request传递给as_view()as_view()再传递给dispatch(),在这里会给原来的request封装用来身份验证的authenticators,他是一个储存认证类对象的列表,封装完成后遍历这个列表,如果抛出exceptions.APIException异常,认证失败,使用匿名用户登录,否则如果返回一个二元组,就将他们分别赋值给user和auth,如果返回None,同样认证失败,使用匿名用户登录。

全局验证

可以设置对所有视图验证,因为

authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
def reload_api_settings(*args, **kwargs):
    setting = kwargs['setting']
    if setting == 'REST_FRAMEWORK':
        api_settings.reload()

所以在Django的配置文件中添加

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': ['demo.utils.MyAuthentication.MyAuthentication']
}

就可以设置所有视图都要使用MyAuthentication验证,如果由别的视图不需要验证,可在视图类内把authentication_classes设置为空列表。