rest_framework框架之认证功能的使用和源码实现流程分析

rest_framework框架之认证的使用和源码实现流程分析

一、认证功能的源码流程

  • 创建视图函数

Note
创建视图函数后,前端发起请求,url分配路由,执行视图类,视图类中执行对应方法必须经过dispatch()即调度方法

from rest_framework.views import APIView
from django.shortcuts import HttpResponse
import json

class DogView(APIView):
def get(self, request, *args, **kwargs):
result = {
'code': '10000',
'msg': '数据创建成功'
}
return HttpResponse(json.dumps(result))

def post(self, request, *args, **kwargs):
return HttpResponse('创建一条订单')

def put(self, request, *args, **kwargs):
return HttpResponse('更新一条订单')

def delete(self, request, *args, **kwargs):
return HttpResponse('删除一条订单')

  • 运行dispatch方法

Note
如果自己定义了dispatch方法,则程序运行自定义方法,如果没有,程序运行源码中的dispatch方法。从dispatch方法中可以找到原生request在作为参数传递后被initialize_request()函数进行了加工,通过加工的request获得的值包括原生的request和BaseAuthentication实例化对象,所以我们需要找到initialize_request()。

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)
'''
对原生的request进行加工,获得到的request已经不是原来的request,还包括了其他的参数,
可以通过新的request获取到内部包含的参数
加工后的request : Restquest(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),

negotiator=self.get_content_negotiator(),
parser_context=parser_context
))
'''
self.request = request
self.headers = self.default_response_headers # deprecate?

try:
self.initial(request, *args, **kwargs)
# 把加工后的request当作参数传递给了initial()函数
# 需要把在这里查找initial()函数
# 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()函数

Note
在initialize_request()函数中返回了authenticators, 通过观察可以看出,authenticators的值来自于另外一个函数get_authenticators()。

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

return Request(
request,
# 原生request
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
# authenticators获取到的是实例化后的认证类对象列表,即[Foo(), Bar()]
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)

  • 找到函数self.get_authenticators()

Note
这个函数中实质上是把一个认证类列表实例化为对象列表进行返回,这里就可以得出在上一个函数中的authenticators是一个实例化对象列表。需要继续往源头找,查找authentication_classes

def get_authenticators(self):
"""
Instantiates and returns the list of authenticators that this view can use.
"""
# 例如self.authentication_classes = [foo, bar]
return [auth() for auth in self.authentication_classes]
# 列表生成式,auth获取到的是列表中的类,auth()是把获取到的类对象进行实例化操作

  • 查找authentication_classes类

Note
在自己编写的代码中如果定义了认证类,则执行自定义认证类,如果没有定义authentication_classes类,程序会从继承的类中去查找,视图类继承自APIView,所以在APIView中找到类authentication_classes。

class APIView(View):

The following policies may be set at either globally, or per-view.

renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
parser_classes = api_settings.DEFAULT_PARSER_CLASSES
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
# 继承自APIView中的api_settings.DEFAULT_AUTHENTICATION_CLASSES类
throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
metadata_class = api_settings.DEFAULT_METADATA_CLASS
versioning_class = api_settings.DEFAULT_VERSIONING_CLASS

Summary
从上述的逻辑可以看出最终要执行的是AUTHENTICATION_CLASSES,所有的程序中都是如果有自定义程序会覆盖掉框架封装好的,没有自定义,程序才会执行封装好的代码。AUTHENTICATION_CLASSES类是这个逻辑中最重要的一环。

  • 上边的代码查找到了最基本的Authentication_classes,并且得到加工后的request包含两部分内容:原生的request、Authentication_classes实例化后得到的对象列表,此时需要继续执行dispatch(),执行到try语句时,加工后的request作为参数传递给initial()函数,并执行该函数,此时需要到request.py中查找initial()函数。

    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

  • 查找initial()方法,在该方法中找到perform_authentication(request)方法,继续查找perform_authentication(request)方法

    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)

  • perform_authentication方法中调用了request.py中的Request类的user()方法

    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类中查找到request被传递进行,原生的参数在调用的时候格式为:request._request, 加工后的直接是request.属性

    class Request:
    """
    Wrapper allowing to enhance a standard HttpRequest instance.

    Kwargs:
    - request(HttpRequest). The original request instance.
    - parsers_classes(list/tuple). The parsers to use for parsing the
    request content.
    - authentication_classes(list/tuple). The authentications used to try
    authenticating the request's user.
    """

    def init(self, request, parsers=None, authenticators=None,
    negotiator=None, parser_context=None):
    assert isinstance(request, HttpRequest), (
    'The request argument must be an instance of '
    'django.http.HttpRequest, not {}.{}.'
    .format(request.class.module, request.class.name)
    )

    self._request = request
    # 加工后的request被作为参数传递,那么传递后相对于本类即为原生的request。
    self.parsers = parsers or ()
    self.authenticators = authenticators or ()
    self.negotiator = negotiator or self._default_negotiator()
    self.parser_context = parser_context
    self._data = Empty
    self._files = Empty
    self._full_data = Empty
    self._content_type = Empty
    self._stream = Empty

  • 如果进行认证,必须通过user,此时需要查找user程序是否存在,在Request类中找到了user方法,user()方法执行了_authenticate(),查找_authenticate()

    @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()
    # 执行_authenticate()
    return self._user

  • 查找_authenticate(),在_authenticate()方法中查找到Authenticator_classes生成的实例化列表类对象,循环的对象具有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:
    raise self._not_authenticated()
    # 没有返回值则抛出_not_authenticated()异常

    if user_auth_tuple is not None:
    self._authenticator = authenticator
    self.user, self.auth = user_auth_tuple
    # authenticate()方法返回的元组存在,那么把元组的内容分别赋值给user, auth
    return self._not_authenticated()

  • 查找异常处理方法_not_authenticated(),当前边的方法判断后没有收到元组数据,程序抛出了异常,这个异常执行_not_authenticated()方法,方法中直接调用框架自定义的api_settings.UNAUTHENTICATED_USER()类,如果存在user为AnonymousUser(匿名用户), auth为None,如果不存在,user和auth都直接赋值为None。

    def _not_authenticated(self):
    """
    Set authenticator, user & authtoken representing an unauthenticated request.

    Defaults are 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

二、自定义认证类

通过上述逻辑的整体分析,我们可以编写一个自定义的认证类供视图函数来调用,自定义的认证类必须具有两个方法:authenticate()和authenticate_header()方法,authenticate()必须返回一个元组,元组第一个元素为user,第二个元素为token对象

为测试程序临时创建的数据

ORDER_DICT = {
1: {
'name': 'dog',
'age': 2,
'gender': 'male'
},
2: {
'name': 'cat',
'age': 3,
'gender': 'female'
}
}

自定义Authentication_classes

from rest_framework import exceptions
from api.models import UserToken

class MyAuthentication(object):
def authenticate(self, request, *args, **kwargs):
token = request._request.GET.get('token')
token_obj = UserToken.objects.filter(token=token).first()
if not token_obj:
raise exceptions.AuthenticationFailed("用户尚未登陆")
return (token_obj.user, token_obj)

def authenticate_header(self, request):
pass

生成随机字符串token

def md5(username):
# 以用户post传过来的username和时间来作为参数,随机生成token,
# 需要注意的是在创建models是username字段必须是唯一的。
import time
import hashlib

ctime = str(time.time)
m = md5.hashlib(ytes(username, encodig='utf-8'))
# 生成的随机字符串编码为utf-8
m.update(bytes(ctime, encoding='utf-8'))
return m.hexdigest()

创建认证视图

from rest_framework.views import APIView
from api.models import UserInfo
from django.http import JsonResponse

class AuthView(APIView):
def post(self, request, *args, **kwargs):
# 虽然是验证信息,也是需要用户提交过来信息的,所以这里是post方法
result = {
'code': '1000',
'msg': None
}
try:
username = request._request.GET.get('username')
password = request._request.GET.get('password')
user_obj = UserInfo.objects.filter(username=username, password=password).first()
if not user_obj:
result['code'] = '1001'
result['msg'] = "用户不存在"
# 如果不存在返回不存在异常
token = md5(username)
# 创建函数生成token(随机字符串)
result['token'] = token
UserToken.objects.update_or_create(user=user_obj, defaults={'token': token})
# 如何实例化对象存在,则创建或者更新token
except Exception as e:
result['code'] = '1002'
result['msg'] = '请求异常'
return JsonResponse(result)

创建处理request的视图函数

class OrderView(APIView):
authentication_classes = [MyAuthentication,]
def get(self, request, *args, **kwargs):
result = {
'code': '1003',
'msg': None,
'data': None
}
try:
result['data'] = ORDER_DICT
except Exception as e:
result['code'] = '1004',
result['msg'] = '请求错误'
return result

Note
在上边自定义的程序中,基本逻辑是:

  • 首先是创建认证视图类,这个类解决的是哪些用户可以访问和获取到数据,认证视图中的思路是: dispatch调度方法获取到request后,进行加工,从加工的request中可以的到原生request通过post方法传过来的username和password信息,通过这些信息调用数据库查找匹配对象,如果没有抛出异常,如果存在,需要设置一个函数生成一个专属token
  • 创建生成token函数,该函数需要用到time和hashlib两个第三方库,以request传过来的username和传入时间为参数进行设置生成
  • 收到生成的token后认证视图将token作为参数返回,同时创建或者更新实例化对象的token字段信息,在用户再次登陆后传过来的信息中就自动包含token
  • 创建处理request的视图类,视图类中调用已经自定义好的authentication_classes,这个类专门用于认证信息,在该类中接收到token信息,并与数据库中的验证,如果验证不一致,抛出异常,反之,则返回一个元组信息,并继续执行视图类。需要注意的是,authentication_classes中可以存在多个自定义的认证类,但一般用使用的都是一个。
  • 验证成功后dispatch调度方法执行对应的方法,并返回值给前端页面。

框架内置的认证类

  • BaseAuthentication
  • BaseAuthentication类中是两个方法authenticate()和authenticate_header(), 我们在自定义认证类的时候需要继承自基类,并且对这两个进行重写,如果不重写,系统自动抛出异常。
  • 其他认证类:BasicAuthentication认证
    一般程序中用到的是我们自定义的认证类来进行开发

自定义认证类的使用方式

  • 方式一:全局使用,需要在settings.py文件中设置

    REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
    # 'rest_framework.authentication.BasicAuthentication',
    # 'rest_framework.authentication.SessionAuthentication',
    'api.views.Authentication'
    # 这里是通过路径的方式把自定义的认证类加载到全局文件中

    ]
    }

  • 方式二:局部使用,需要在视图类中调用具体的自定义认证类

    class OrderView(APIView):
    '''
    用于订单相关业务
    '''
    authentication_classes = [Authentication,]
    def get(self, request, *args, **kwargs):
    result = {
    'code': '1000',
    'msg': None,
    'data': None
    }
    try:
    result['data'] = ORDER_DICT
    except Exception as e:
    result['code': '1001']
    result['msg': '访问出错']
    return JsonResponse(result)

posted @ 2019-12-31 16:24  大道至诚  阅读(566)  评论(0)    收藏  举报