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)
来源: https://www.cnblogs.com/ddzc/p/12125070.html