04 DRF-认证

在开发API过程中,有些功能需要登录才能访问,有些无需登录。drf中的认证组件主要就是用来实现此功能。

关于认证组件,我们用案例的形式,先来学习常见的用用场景,然后再来剖析源码。

1 案例1

项目要开发3个接口,其中1个无需登录接口、2个必须登录才能访问的接口。

image

image

image

image

在浏览器上中访问:/order/token=xxxdsfsdfdf
认证组件中返回的两个值,分别赋值给:request.userrequest.auth


2 案例2

项目要开发100个接口,其中1个无需登录接口、99个必须登录才能访问的接口。
此时,就需要用到drf的全局配置(认证组件的类不能放在视图view.py中,会因为导入APIView导致循环引用)。

settings.py

REST_FRAMEWORK = {
    "UNAUTHENTICATED_USER": None,
    "DEFAULT_AUTHENTICATION_CLASSES": ['ext.auth.MyAuthentication']
}

如果此时你在视图类中,也配置了如下

authentication_classes = [MyAuthentication, ]

在drf中,优先去全局中读取,在去视图类中读取。视图类中没有写,默认读全局。如果全局和视图类都存在,则按视图类的中的认证为准

注意:认证类一定不要写在views视图中,不然会报错。

image


3 案例3

项目要开发100个接口,其中1个无需登录接口、98个必须登录才能访问的接口、1个公共接口(未登录时显示公共/已登录时显示个人信息)。

image


4 案例4

项目要开发100个接口,其中1个无需登录接口、98个必须登录才能访问的接口、1个公共接口(未登录时显示公共/已登录时显示个人信息)。
原来的认证信息只能放在URL中传递,如果程序中支持放在很多地方,例如:URL中、请求头中等。

认证组件中,如果是使用了多个认证类,会按照顺序逐一执行其中的authenticate方法

  • 返回None或无返回值,表示继续执行后续的认证类
  • 返回 (user, auth) 元组,则不再继续并将值赋值给request.user和request.auth
  • 抛出异常 AuthenticationFailed(...),认证失败,不再继续向后走。

image


5 源码分析

点击查看代码
class Request:
    def __init__(self, request, authenticators=None):
        self.authenticators = authenticators or ()

    @property
    def user(self):
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                self._authenticate()
        return self._user

    @user.setter
    def user(self, value):
        self._user = value
        self._request.user = value

    def _authenticate(self):
        # 读取每个认证组件的对象,执行 authenticate 方法,self=request对象
        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()

    def _not_authenticated(self):  # 匿名访问
        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


class APIView(View):
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES

    def perform_authentication(self, request):
        request.user

    def initial(self, request, *args, **kwargs):
        self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)


    def get_authenticators(self):
        return [auth() for auth in self.authentication_classes]


    def initialize_request(self, request, *args, **kwargs):
        parser_context = self.get_parser_context(request)

        return Request(
            request,
            authenticators=self.get_authenticators(),  # [对象1, 对象2,...]
            ...
        )


    def dispatch(self, request, *args, **kwargs):
    self.args = args  # url传递的参数接收
    self.kwargs = kwargs  # url传递的参数接收

    # 第一步:请求的封装(django的request对象 + authenticators认证组件)—> 加载认证组件的过车用
    request = self.initialize_request(request, *args, **kwargs)
    self.request = request
    self.headers = self.default_response_headers  # deprecate?

    try:
        # 第二步:request
        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


class UserView(APIView):
    authentication_classes = []
    def get(self, request):
        print(request.user, request.auth)
        return Response('UserView.')

# 请求到来时:
obj = UserView()
obj.dispatch()

ps:

多个认证类:
    - 都返回None,都没有认证成功 -> 视图会被执行,只不过 self.user=None self.auth=None
    - 案例:
        class MyAuthentication(BaseAuthentication):
            def authenticate(self, request):
                token = request.query_params.get('token')  # 获取url中的参数
                if token:
                    return 'lxx', token
                return

        class HeaderAuthentication(BaseAuthentication):
            def authenticate(self, request):
                token = request.meta.get('token')  # 获取url中的参数
                if token:
                    return 'lxx', token
                return

        class BodyrAuthentication(BaseAuthentication):
            def authenticate(self, request):
                token = request.data.get('token')  # 获取url中的参数
                if token:
                    return 'lxx', token
                return

        class NoAuthentication(BaseAuthentication):
            def authenticate(self, request):
                raise AuthenticationFailed({'code': 101, 'error': '认证失败'})

        class UserView(APIView):
            authentication_classes = [MyAuthentication, HeaderAuthentication, BodyrAuthentication, NoAuthentication]

            def get(self, request):
                print(request.user, request.auth)
                return Response('UserView.')

6 authenticate_header

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed

class MyAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # 去做用户认证:
        # 1.读取请求传递的token
        # 2.校验合法性
        # 3.返回值
        #   3.1 返回元组(11, 22)  认证成功  request.user  request.auth
        #   3.2 抛出异常            认证失败  -> 返回错误信息
        #   3.2 返回None,找下一个认证类  多个认证类 [类1, 类2, ...]  直到不返回None时结束 --> 都返回None (匿名用户)

        # /xx/xx/xx/?token=123123123
        # token = request._request.GET.get('token')
        token = request.query_params.get('token')  # 获取url中的参数
        if token:
            return 'lxx', token
        # raise AuthenticationFailed('认证失败.')
        raise AuthenticationFailed({'code': 101, 'error': '认证失败'})

    def authenticate_header(self, request):
        # 没写该方法:如果访问失败,返回的状态码是403
        # 写了该方法:如果访问失败,返回的状态码是401
        return 'token'  # 随便返回一个值就行
		# return 'Basic realm="API"'  # 会跳出弹出框

image


7 扩展-抽象类

父类约束子类中必须要有相应方法,那么必须定义以下:

class Foo(object):
    # 对子类进行约束,子类中必须要有定义的这个方法(类似于接口,抽象类)
    def f1(self):
        raise NotImplementedError("子类必须要有该方法,不然报错喽")

class News(Foo):
    def f1(self):
        ...

8 小小应用

image

posted @ 2022-10-17 15:54  角角边  Views(129)  Comments(0)    收藏  举报