我在前面的博客里已经讲过了, 我们一般编写 API 的时候用的方式
- class CoursesView(ViewSetMixin,APIView):
- pass
这种方式的有点是, 灵活性比较大, 可以根据自己的业务逻辑来自定义各种组件逻辑.
这边文章, 我将带大家对 认证组件做一个源码分析, 写这边文章的原因也是因为自己虽然知道怎么写, 但是不知道为什么要这么写, 今天看了源码, 所以想把自己的一些心得写出来和大家分享.
先看代码
- class ShoppingCarView(ViewSetMixin, APIView):
- authentication_classes = [LuffyAuthtication,]
- def list(self,request, *args, **kwargs):
- """
- 查看购物车信息
- :param args:
- :param kwargs:
- :return:
- """
- try:
- ret = BaseResponse()
- pay_course_list = []
- # key = 'shoppingcar_%s_%s' % (USERID, '*')
- key = settings.SHOPCAR_FORMAT.format( request.user.id, "*")
- user_key_list = COON.keys(pattern=key) # 取到这个用户对应的所有课程字典 对应的键
- for key in user_key_list:
- # 对应的每个键值 去取每个课程对应的信息 和价格列表
- temp = {
- 'id': COON.hget(key, 'id').decode('utf8'),
- 'name': COON.hget(key, 'name').decode('utf8'),
- 'img': COON.hget(key, 'img').decode('utf8'),
- 'default': COON.hget(key, 'default').decode('utf8'),
- 'price_dict': json.loads(COON.hget(key, 'price_dict').decode('utf8')),
- }
- pay_course_list.append(temp)
- ret.data = pay_course_list
- except Exception as e:
- ret.data = '查看失败'
- ret.code = 00000
- return Response(ret.dict)
- 视图类
- from app01 import models
- from rest_framework.authentication import BaseAuthentication
- from rest_framework.exceptions import AuthenticationFailed
- class LuffyAuthtication(BaseAuthentication):
- def authenticate(self, request):
- """
- 用于做用户认证
- :param request:
- :return:
- """ token = request.query_params.get('token')
- token_obj = models.UserToken.objects.filter(token=token).first()
- if not token_obj:
- raise AuthenticationFailed({'code':10000,'error':'认证失败'})
- return token_obj.user,token_obj
- 认证类
- urlpatterns = [
- url(r'^payment/$', payment.PaymentView.as_view({'post': 'create','put': 'update','get':'list'})),
- ]
- url
- 以上的就是逻辑代码, 就是一个必须要验证为注册用户才能查看购物车的内容.
- 下面来进行源码分析, 来看代码是如何走到我自定义的认证类中的.
- 1. 首先从 url 中分析
- 1. 先来到视图类中的 as.view() 方法
- 而我们的自定义的方法中没有 as.view() 方法, 那就要去父类 ViewSetMixin 和 APIView 中去找, 好看源码
- 2. 分析源码
- 1. 先看 ViewSetMixin 类中
- class ViewSetMixin(object):
- """
- This is the magic.
- Overrides `.as_view()` so that it takes an `actions` keyword that performs
- the binding of HTTP methods to actions on the Resource.
- For example, to create a concrete view binding the 'GET' and 'POST' methods
- to the 'list' and 'create' actions...
- view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
- """
- @classonlymethod
- def as_view(cls, actions=None, **initkwargs):
- """
- Because of the way class based views create a closure around the
- instantiated view, we need to totally reimplement `.as_view`,
- and slightly modify the view function that is created and returned.
- """
- # The suffix initkwarg is reserved for displaying the viewset type.
- # eg. 'List' or 'Instance'.
- cls.suffix = None
- # The detail initkwarg is reserved for introspecting the viewset type.
- cls.detail = None
- # Setting a basename allows a view to reverse its action urls. This
- # value is provided by the router through the initkwargs.
- cls.basename = None
- # actions must not be empty
- if not actions:
- raise TypeError("The `actions` argument must be provided when"
- "calling `.as_view()` on a ViewSet. For example"
- "`.as_view({'get':'list'})`")
- # sanitize keyword arguments
- 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" % (
- cls.__name__, key))
- def view(request, *args, **kwargs):
- self = cls(**initkwargs)
- # We also store the mapping of request methods to actions,
- # so that we can later set the action attribute.
- # eg. `self.action = 'list'` on an incoming GET request.
- self.action_map = actions
- # Bind methods to actions
- # This is the bit that's different to a standard view
- for method, action in actions.items():
- handler = getattr(self, action)
- setattr(self, method, handler)
- if hasattr(self, 'get') and not hasattr(self, 'head'):
- self.head = self.get
- self.request = request
- self.args = args
- self.kwargs = kwargs
- # And continue as usual
- # 前面都是在对传参做判断和重新赋值, 重要的是下面这一步, 最后 return 调用了 dispatch 方法
- return self.dispatch(request, *args, **kwargs)
- 2. 找 dispatch 方法在哪里, 答案肯定是在 APIView 中
- 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(.....)
- self.request = request
- self.headers = self.default_response_headers
- 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 方法里面:
- (1) request = self.initialize_request(request, *args, **kwargs)
- 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(), #[BasicAuthentication(),], 把对象封装到 request 里面了
- negotiator=self.get_content_negotiator(), parser_context=parser_context )
- (2)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]
- (3)authentication_classes
- APIView 里面有个 authentication_classes 字段 , 这里如果写了静态变量就直接在类的静态变量中找, 否则就到全局配置文文件中去找
- 可以看到默认是去全局的配置文件找 (api_settings)
- 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
- 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
- (4) 开始验证
- (5)self.initial(request,*args,**kwargs)
- 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) 频率
- (6)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.user, 那这个 user 方法是谁的呢????
- 还记得这个 Request 吧, 对没错就是他的, 我们点进去看看
- (7)request.user
- (8)self._authenticate() 方法
- def _authenticate(self):
- """
- Attempt to authenticate the request using each authentication instance
- in turn.
- """
- #循环认证类的所有对象
- for authenticator in self.authenticators:
- try:
- #执行认证类的 authenticate 方法
- #这里分三种情况
- #1. 如果 authenticate 方法抛出异常, self._not_authenticated() 执行
- #2. 有返回值, 必须是元组:(request.user,request.auth)
- #3. 返回 None, 表示当前认证不处理, 等下一个认证来处理
- 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()
返回的元组就是上面自定义认证类代码中的
token_obj.user, 可以再后续的 request.user 中取到
token_obj , 可以再后续的 request.auth 中取到
到这里整个认证组件的源码就分析完了
自己写认证类方法梳理
(1) 创建认证类
继承 BaseAuthentication --->>1. 重写 authenticate 方法; 2.authenticate_header 方法直接写 pass 就可以 (这个方法必须写)
(2)authenticate() 返回值 (三种)
None ----->>> 当前认证不管, 等下一个认证来执行, 可用于对请求的过滤上
raise exceptions.AuthenticationFailed('用户认证失败') # from rest_framework import exceptions
有返回值元祖形式:(元素 1, 元素 2) #元素 1 复制给 request.user; 元素 2 复制给 request.auth
(3) 局部使用
authentication_classes = [BaseAuthentication,]
(4) 全局使用
- # 设置全局认证
- REST_FRAMEWORK = {
- "DEFAULT_AUTHENTICATION_CLASSES":['API.utils.auth.Authentication',]
- }
源码流程
--->>dispatch
-- 封装 request
--- 获取定义的认证类 (全局 / 局部), 通过列表生成式创建对象
- ---initial
- ----peform_authentication
- -----request.user (每部循环创建的对象)
来源: https://www.cnblogs.com/yyyyyyyyyy/p/9501427.html