DRF之认证组件

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

1. 快速使用

通过编写类的形式实现,一个类就是一个认证组件

  • 编写类 -> 认证组件
  • 应用组件
from rest_framework.authentication import BaseAuthentication


class MyAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # 去做用户认证
        # 1.读取请求传递的token
        # 2.校验合法性
        # 3.返回值
        # 3.1 返回元组 (111,222) 认证成功 request.user request.auth
        # 3.2 抛出异常           认证失败 -> 返回错误信息
        # 3.1 返回None          多个认证类 [类1,类2,类3,类4,,,] -> 匿名用户
        pass
案例1

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

局部配置 authentication_classes = []

  • 认证失败信息定制

在这里插入图片描述

  • 匿名用户

在这里插入图片描述

  • 认证组件中返回的两个值,分别赋值给:request.userrequest.auth

在这里插入图片描述

  • urls.py
from django.urls import path
from api import views

urlpatterns = [
    path('login/', views.LoginView.as_view()),
    path('user/', views.UserView.as_view()),
    path('order/', views.OrderView.as_view()),

]
  • views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.request import Request
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed


class MyAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # /xxx/xxx/xxx?token=123123123
        # token = request._request.GET.get('token')
        token = request.query_params.get('token')

        if token:
            return "user", token
        # raise AuthenticationFailed("认证失败")
        raise AuthenticationFailed({"code": 20000, "error": "认证失败"})


class LoginView(APIView):
    authentication_classes = []

    def get(self, request):
        print(request.user, request.auth)
        return Response("LoginView")


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

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


class OrderView(APIView):
    authentication_classes = [MyAuthentication, ]

    def get(self, request):
        print(request.user, request.auth)
        return Response("OrderView")

案例2

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

此时,就需要用到drf的全局配置(认证组件的类不能放在视图view.py中,会因为导入APIView导致循环引用,该案例放置在ext/auth.py中)

drf会先加载全局配置,在加载局部配置,局部配置会覆盖全局配置

  • ext\auth.py
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed


class MyAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # /xxx/xxx/xxx?token=123123123
        # token = request._request.GET.get('token')
        token = request.query_params.get('token')

        if token:
            return "user", token
        # raise AuthenticationFailed("认证失败")
        raise AuthenticationFailed({"code": 20000, "error": "认证失败"})

  • urls.py
from django.urls import path
from api import views

urlpatterns = [
    path('login/', views.LoginView.as_view()),
    path('user/', views.UserView.as_view()),
    path('order/', views.OrderView.as_view()),

]
  • views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from ext.auth import MyAuthentication


class LoginView(APIView):
    authentication_classes = []

    def get(self, request):
        print(request.user, request.auth)
        return Response("LoginView")


class UserView(APIView):

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


class OrderView(APIView):

    def get(self, request):
        print(request.user, request.auth)
        return Response("OrderView")

案例3

状态码一致性问题:
如果认证组件中未定义,则统一为 HTTP_403_FORBIDDEN

如果自定义了,还会加上一个响应头

在这里插入图片描述

在这里插入图片描述

  • 参考源码
def handle_exception(self, exc):

        if isinstance(exc, (exceptions.NotAuthenticated,exceptions.AuthenticationFailed)):
            auth_header = self.get_authenticate_header(self.request)
            if auth_header:
                exc.auth_header = auth_header
            else:
                exc.status_code = status.HTTP_403_FORBIDDEN
        return response

    def get_authenticate_header(self, request):
        authenticators = self.get_authenticators()
        if authenticators:
            return authenticators[0].authenticate_header(request)
案例4

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

原来的认证信息只能放在URL中传递,如果程序中支持放在很多地方,例如:URL中、请求头中等。

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

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

多个认证组件每一个都返回None,都没有认证成功,视图函数也会被执行,只不过self.user,self.auth为None

  • ext\auth.py
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed


class ParamAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get('token')

        if not token:
            return
        return "user", token

    def authenticate_header(self, request):
        return 'Token'


class HeaderAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.META.get('HTTP_AUTHORIZATION')

        if not token:
            return
        return "user", token

    def authenticate_header(self, request):
        return 'Token'


class NotAuthentication(BaseAuthentication):
    def authenticate(self, request):
        raise AuthenticationFailed({"code": 20000, "error": "认证失败"})

    def authenticate_header(self, request):
        return 'Token'

  • views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from ext.auth import ParamAuthentication, HeaderAuthentication


# 无需登录
class UserView(APIView):
    authentication_classes = []

    def get(self, request):
        return Response("用户信息")


# 公共接口
class OrderView(APIView):
    authentication_classes = [ParamAuthentication, HeaderAuthentication, ]

    def get(self, request):
        if request.user:
            message = f"{request.user}的订单信息"

        else:
            message = "公共订单信息"
        return Response(message)


# 必须登录
class InfoView(APIView):
    def get(self, request):
        message = f"{request.user}的用户信息"
        return Response(message)

  • settings.py
REST_FRAMEWORK = {
    "UNAUTHENTICATED_USER": None,  # 未登录时,request.user=None
    "UNAUTHENTICATED_TOKEN": None,  # 未登录时,request.auth=None
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "ext.auth.ParamAuthentication",
        "ext.auth.HeaderAuthentication",
        "ext.auth.NotAuthentication",
    ]
}

2. 面向对象-继承

class APIView(View):
    authentication_classes = 读取配置文件中的列表

    def dispatch(self):
        self.authentication_classes


class UserView(APIView):
    authentication_classes = [11,22,33,44]


obj = UserView()
obj.dispatch()  # authentication_classes = [11,22,33,44]

3. 组件源码

在这里插入图片描述

class Request:
    def __init__(self, request, authenticators=None):
        self._request = request
        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):
        # authenticator是认证类实例化后的对象,循环逐一认证
        # 如果认证过程抛出异常,认证不通过
        # 若认证过程没有抛出异常,也没有返回self.user, self.auth,则匿名执行视图函数
        for authenticator in self.authenticators:
            try:
                user_auth_tuple = authenticator.authenticate(self)  # 调用认证类的authenticate方法,返回元组
            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 as_view(cls, **initkwargs):
        view = super().as_view(**initkwargs)
        return csrf_exempt(view)

    def dispatch(self, request, *args, **kwargs):
        # 第一步:请求的封装(django的 request + drf的 authenticators认证组件)  --> 加载认证组件过程
        # request.authenticators  [对象1,对象2,...]
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        # 第二步:认证过程
        try:
            self.initial(request, *args, **kwargs)
            handler = getattr(self, request.method.lower(), 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

    def initialize_request(self, request, *args, **kwargs):
        return Request(
            request,
            authenticators=self.get_authenticators(),  # [对象1,对象2,...]
        )

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

    def perform_authentication(self, request):
        request.user

    def get_authenticators(self):
        # 返回一个列表每一个元素都是认证类对象 [对象1,对象2,...]
        return [auth() for auth in self.authentication_classes]  # 优先读取局部配置,后全局配置

    def handle_exception(self, exc):

        if isinstance(exc, (exceptions.NotAuthenticated,exceptions.AuthenticationFailed)):
            auth_header = self.get_authenticate_header(self.request)
            if auth_header:
                exc.auth_header = auth_header
            else:
                exc.status_code = status.HTTP_403_FORBIDDEN
        return response

    def get_authenticate_header(self, request):
        authenticators = self.get_authenticators()
        if authenticators:
            return authenticators[0].authenticate_header(request)

class View:
    def as_view(cls, **initkwargs):
        def view(request, *args, **kwargs):
            return self.dispatch(request, *args, **kwargs)

        return view


class UserView(APIView):
    def get(self, request):
        print(request.user, request.auth)
        return Response("UserView")

4. 拓展-子类约束

class Foo(object):
    # 对子类进行约束,约束子类中必须要定义该方法,不然就报错
    def f1(self):
        raise NotImplementedError("...")


class Bar(Foo):
    def f1(self):
        print("123")
  • BaseAuthentication类定义
class BaseAuthentication:
    """
    All authentication classes should extend BaseAuthentication.
    """

    def authenticate(self, request):
        """
        Authenticate the request and return a two-tuple of (user, token).
        """
        raise NotImplementedError(".authenticate() must be overridden.")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值