【10.0】DRF之登录认证和权限频率组件

发布时间 2023-07-31 12:29:36作者: Chimengmeng

【准备数据】

from django.db import models


# Create your models here.
class UserInfo(models.Model):
    name = models.CharField(max_length=32)
    password = models.CharField(max_length=64)


class UserToken(models.Model):
    token = models.CharField(max_length=64)
    user = models.OneToOneField(to=UserInfo, on_delete=models.CASCADE)

【一】登录功能

【1】登录视图

from django.shortcuts import render
from rest_framework.viewsets import ViewSet
from app01.models import UserToken, UserInfo
from rest_framework.response import Response
from rest_framework import status
import uuid
from rest_framework.decorators import action


# Create your views here.
# 登录接口
class UserView(ViewSet):
    back_dict = {"code": 100, "msg": "", "result": ""}

    @action(methods=["POST"], detail=False)
    def login(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        user_obj = UserInfo.objects.filter(name=username, password=password).first()
        if user_obj:
            # 登陆成功
            # (1)生成随机字符串 - 永不重复的随机字符串(极小概率会重复)
            token = str(uuid.uuid4())
            self.back_dict["code"] = 100
            self.back_dict["msg"] = "登陆成功"
            self.back_dict["token"] = token
            '''
             # (2)把生成的token存储到数据库中,UserToken有值就更新,没有则新增
            token_obj = UserToken.objects.filter(user_id=user_obj.pk).first()
            if token_obj:
                # (2.1)更新token
                token_obj.update(token=token)
            else:
                UserToken.objects.create(token=token)
            '''
            # (2)把生成的token存储到数据库中,UserToken有值就更新,没有则新增
            # update_or_create(查询条件,更新或新增的数据): 根据用户名去 UserToken 表里面查询数据
            # 如果存在则更新成后面的数据,如果不存在则新建一条数据
            UserToken.objects.update_or_create(user=user_obj, defaults={"token": token})
            return Response(self.back_dict, status=status.HTTP_200_OK)
        else:
            # 登陆失败
            self.back_dict["code"] = 101
            self.back_dict["msg"] = "登陆失败,用户名或密码错误"
            return Response(self.back_dict, status=status.HTTP_422_UNPROCESSABLE_ENTITY)

【2】路由接口

  • 注册方式一
    • 手动注册
      • 不需要配合视图函数中的action装饰器使用
from django.contrib import admin
from django.urls import path
from app01.views import UserView

urlpatterns = [
    path('admin/', admin.site.urls),
    # 登录路由 - 直接注册
    # 访问地址:http://127.0.0.1:8000/login/
    path('login/', UserView.as_view({"post": "login",})),
]
  • 注册方式二
    • 自动注册
      • 需要配合视图函数中的action装饰器使用
from django.contrib import admin
from django.urls import path
from app01.views import UserView
from rest_framework.routers import SimpleRouter

router = SimpleRouter()
router.register("login", UserView.as_view())

urlpatterns = [
    path('admin/', admin.site.urls),
  
]

# 登录路由 - 自动注册
# http://127.0.0.1:8000/user/login/
urlpatterns += router.urls

【3】后端返回数据结果

{
    "code": 100,
    "msg": "登陆成功",
    "result": "",
    "token": "5a89a857-c6f7-49f1-807a-ef3c6f34e2ea"
}

【补充】为什么能用user=user.obj

  • UserToken表中有user字段
  • 我们通过UserToken表拿到了一个UserToken的对象
    • user_token.token 就是字符串
    • user_token.user
      • 基于对象的跨表查询,拿到的就是user对象
      • 可以 user_token.user.属性(UserInfo表) 取值
    • user_token.user_id
      • 隐藏了这个字段,是可以使用的字段,它是管理的user对象的id值
  • 查询功能
    • UserToken.objects.update_or_create(user=对象, defaults={"token": token})
    • UserToken.objects.update_or_create(user_id=对象.pk, defaults={"token": token})
  • 通过UserToken.objects.update_or_create(user=对象, defaults={"token": token})UserToken.objects.update_or_create(user_id=对象.pk, defaults={"token": token})可以进行查询和更新操作。

  • 在这里

    • user字段是UserToken表中的一个外键字段,它指向了UserInfo表中的用户对象。
    • 当你使用UserToken.objects.update_or_create(user=对象, defaults={"token": token})时,将会根据提供的对象来查找或创建与之对应的UserToken对象,并更新或创建其token字段的值。
  • 类似地

    • 当你使用UserToken.objects.update_or_create(user_id=对象.pk, defaults={"token": token})时,将会根据提供的对象.pk(即用户对象的id值)来查找或创建与之对应的UserToken对象,并更新或创建其token字段的值。
  • 这两种方式都可以实现基于对象的跨表查询

    • 通过user属性可以访问到UserInfo表中的相关信息。
    • 这是因为在UserToken表中,通过外键关系与UserInfo表建立了联系,使得你可以通过user_token.user来获取与之关联的UserInfo表中的用户对象。
  • 总结起来

    • 上述内容介绍了通过user字段和user_id字段来操作UserToken表的查询功能,并说明了如何利用这些字段进行基于对象的跨表查询。

【二】认证组件

  • APIView执行流程
    • 在视图视图类的方法之前,执行了三大认证

【1】认证:登录认证

登录认证--->控制,某个接口必须登录后才能访问

  • 登录认证是一种权限控制机制,用于确保只有已经登录的用户才能访问某个接口或资源。
  • 在Django REST Framework中,登录认证是在APIView执行流程中的第一步。

(1)简解

  • 当客户端发送请求时,Django REST Framework首先会调用APIView的dispatch方法。
  • 在dispatch方法中,将按照定义的顺序执行设置的认证类的认证操作。
  • 在认证过程中,首先执行登录认证。登录认证类的主要作用是验证请求中所携带的身份信息是否有效,并且关联该身份信息对应的用户对象。
  • Django REST Framework提供了多种登录认证类可供选择,例如基于Token的认证,基于Session的认证,OAuth认证等。开发人员可以根据项目需求选择适合的认证类。
  • 如果认证成功,则会将认证后的用户对象保存到请求的user属性中,方便后续的权限检查和授权操作使用。
  • 如果认证失败,则会返回相应的错误提示,通常是HTTP 401 Unauthorized状态码,表示需要进行登录认证才能访问该接口。

(2)小结

  • 登录认证是APIView执行流程的第一步,在控制访问接口之前验证请求中的身份信息是否有效,只有登录成功的用户才能继续访问后续的接口或资源。
  • 通过选择适当的登录认证类,可以实现不同的登录认证方式,提高系统的安全性和灵活性。

【2】认证组件使用步骤(固定用法)

(1)创建登陆认证类

  • 创建一个类,并继承BaseAuthentication。这个类将作为自定义认证组件的基类。

(2)实现authenticate方法

  • 在类中实现authenticate方法。
    • 在这个方法中,完成登录认证的逻辑。
    • 如果请求中没有提供有效的登录凭证或认证失败,可以抛出相应的异常。

(3)认证逻辑

  • 如果认证成功,应返回一个包含认证用户和令牌(token)的元组(user, token)

(4)使用认证类

  • 在视图类中,使用认证类

    • 局部使用:

      • 在视图类中设置authentication_classes属性为需要使用的认证组件列表。
      class TokenView(APIView):
          # 被 LoginAgain 管理的类才能访问
          authentication_classes = [LoginAgain,]
      
    • 全局使用:

      • 在项目的配置文件(settings.py)中,配置项目默认认证组件的路径。
      REST_FRAMEWORK = {
          'DEFAULT_AUTHENTICATION_CLASSES': [
              'app01.login_again.LoginAgain'
          ],
      
      }
      

(5)全局使用/局部禁用

  • 当认证组件被全局配置后,可以在视图类中禁用全局认证组件,以实现局部禁用。

    • 如果登陆认证组件被全局配置,你仍然可以在某个视图类中禁用全局的登陆认证,实现局部禁用的效果。只需在该视图类中将authentication_classes属性设置为空列表即可。
    class UserView(ViewSet): 
        authentication_classes = []       
    

(6)认证类的使用顺序

认证类的使用顺序

  • 优先用视图类配置的

  • 其次用项目配置文件

  • 最后用drf默认的配置

  • 认证类的使用顺序

    • 首先查找视图类中通过authentication_classes属性设置的认证组件列表,按照列表中的顺序进行认证。

    • 如果视图类没有设置认证组件列表,则会查找项目配置文件中REST_FRAMEWORK设置的DEFAULT_AUTHENTICATION_CLASSES认证组件列表。

    • 如果以上两个地方都没有设置认证组件列表,则使用DRF默认的认证组件。

[补充]一旦通过认证,在request中就有当前登录用户

def get(self, request):
    # 一旦通过认证,在request中就有当前登录用户
    print(request.user.name,'访问了接口')

【3】示例

  • 认证类
# -*-coding: Utf-8 -*-
# @File : login_again .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/7/30
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed

from app01.models import UserToken


class LoginAgain(BaseAuthentication):
    def authenticate(self, request):
        '''
         def authenticate(self, request):
        """
        Authenticate the request and return a two-tuple of (user, token).
        """
        raise NotImplementedError(".authenticate() must be overridden.")

        父类中有这个方法,并且必须被重写,否则会报错
        '''
        # (1)校验用户是否登录 --- 请求中携带了服务端返回的随机token
        # token携带在哪,是由接口规定的
        ### 可以规定携带在请求地址中
        ### 可以规定携带在请求头中(一般带在头中)
        ### 可以规定携带在请求体中

        # 取出token
        token = request.query_params.get('token')
        # 在数据库中取出token进行校验
        user_token = UserToken.objects.filter(token=token).first()
        if user_token:
            # 当前登录用户就是user
            user_obj = user_token.user
            return user_obj, token
        else:
            # 错误的token
            raise AuthenticationFailed("请先登录!谢谢!")
  • 请求携带在哪里?

    • 携带在请求地址中:

      • 将token直接附加在请求地址的参数中。
      • 例如:https://example.com/api?token=随机token
      • 在这个示例中,token信息通过在URL中的?token=后面添加具体的token值来进行传递。
    • 携带在请求头中:

      • 将token放置在HTTP请求头(Header)中,一般使用自定义的字段进行传递。

      • 例如,在请求头中添加一个字段Authorization来携带token:

        Authorization: Bearer 随机token
        
      • 在服务端代码中,可以通过读取请求头中的HTTP_TOKEN字段来获取token值。

      • 例如,在Python Django框架中获取token的示例代码如下:

        token = request.META.get('HTTP_TOKEN')
        
    • 携带在请求体中:

      • 将token作为请求体的一部分发送给服务端。

      • 这种方式通常在使用POST或PUT等请求方法时使用,可以将token放置在请求体的某个参数中。

      • 例如,使用JSON格式的请求体:

        {
          "token": "随机token"
        }
        
      • token信息放置在请求体(body)中的一个参数token中进行传递。

  • 在视图函数中使用认证类
# 认证组件
# 限制登录用户才能访问接口获取数据
# 判断登录的条件:登录成功后返回给前端一个随机token字符串,当第二次请求的时候只要携带了这个随机字符串,并且我能根据这个随机字符串在我的数据库中查询到这条数据即可判定为登陆过
from login_again import LoginAgain


class TokenView(APIView):
    back_dict = {"code": 100, "msg": "", "result": ""}
    # 被 LoginAgain 管理的类才能访问
    authentication_classes = [LoginAgain,]

    def get(self, request):
        token_obj = UserToken.objects.all()
        self.back_dict['msg'] = "请求数据成功"
        self.back_dict["result"] = token_obj.values()
        return Response(self.back_dict)
  • 前端访问必须携带token
http://127.0.0.1:8000/token/?token=9ceaab7c-d119-448a-92e8-c2f302d0b869
  • 携带token成功后返回结果

    {
        "code": 100,
        "msg": "请求数据成功",
        "result": [
            {
                "id": 1,
                "token": "9ceaab7c-d119-448a-92e8-c2f302d0b869",
                "user_id": 1
            }
        ]
    }
    
  • 无token失败后返回结果

    {
        "detail": "请先登录!谢谢!"
    }
    

【三】权限组件

  • 登录后的用户要根据用户角色的不同决定当前用户能访问功能的全选
    • 部分功能只对管理员开放
    • 不分功能只对普通用户开放
  • 权限设计:比较复杂
    • ACL(Access Control List)
    • RBAC(Role-Based Access Control)
    • ABAC(Attribute-Based Access Control)
    • ...

【1】修改数据表结构

  • 新增字段
    • 当前用户角色
from django.db import models


# Create your models here.
class UserInfo(models.Model):
    name = models.CharField(max_length=32)
    password = models.CharField(max_length=64)
    user_roles = models.IntegerField(choices=((0, 'admin'), (1, '普通用户')), default=1)


class UserToken(models.Model):
    token = models.CharField(max_length=64)
    user = models.OneToOneField(to=UserInfo, on_delete=models.CASCADE)

【2】权限组件使用步骤(固定用法)

(1)创建权限认证类

  • 创建一个自定义的权限认证类,并让它继承自BasePermission

(2)实现has_permission方法

  • 在类中写方法:has_permission

    • 如果有权限,就返回True
    • 如果没有权限,就返回False
    • 错误信息是self.message='字符串'

(3)权限认证逻辑

# -*-coding: Utf-8 -*-
# @File : permission .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/7/30
from rest_framework.permissions import BasePermission


class AdminPermission(BasePermission):
    '''
        def has_permission(self, request, view):
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        return True
    '''

    def has_permission(self, request, view):
        # 如果有权限,返回True,没有则返回False
        # 判断用户表中的角色信息
        # 对应数据库中的字段类型  IntegerField
        if request.user.user_roles == 0:
            return True
        else:
            return False

(4)使用权限认证类

  • 在视图类中,使用认证类

    • 局部使用
      • 在视图类中添加permission_classes属性,并将所需的权限认证类作为其值
      • 这样,在该视图类中只会应用指定的权限认证类。
    # 权限认证
    class UserDetailView(APIView):
        permission_classes = [AdminPermission, ]
    
    • 全局使用
      • 在配置文件(一般是settings.py)中进行全局配置,将权限认证类添加到DEFAULT_PERMISSION_CLASSES中。
      • 这样,在所有视图类中都会应用该权限认证类。
    REST_FRAMEWORK = {
        'DEFAULT_PERMISSION_CLASSES': [
            'app01.permission.AdminPermission'
        ],
    }
    

(5)全局使用/局部禁用

  • 当认证组件被全局配置后,可以在视图类中禁用全局认证组件,以实现局部禁用。
    • 如果权限认证组件被全局配置,你仍然可以在某个视图类中禁用全局的权限认证,实现局部禁用的效果。
    • 只需在该视图类中将permission_classes属性设置为空列表即可。
# 权限认证 - 局部禁用
class UserDetailView(APIView):
    permission_classes = []

(6)权限认证类的使用顺序

【3】示例

  • 书写组件认证类
# -*-coding: Utf-8 -*-
# @File : permission .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/7/30
from rest_framework.permissions import BasePermission


class AdminPermission(BasePermission):
    '''
        def has_permission(self, request, view):
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        return True
    '''

    def has_permission(self, request, view):
        # 如果有权限,返回True,没有则返回False
        # 判断用户表中的角色信息
        # 对应数据库中的字段类型  IntegerField
        if request.user.user_roles == 0:
            return True
        return False
  • 用户视图加权限
from app01.permission import AdminPermission


# 权限认证
class UserDetailView(APIView):
    back_dict = {"code": 100, "msg": "", "result": ""}
    permission_classes = [AdminPermission, ]

    def delete(self, request, *args, **kwargs):
        # 一旦通过认证,在request中就有当前用户信息
        print(f"{request.user.name}:>>这是删除功能")

        self.back_dict['msg'] = f"{request.user.name}访问删除功能正常"
        return Response(self.back_dict)
  • 路由
path('user/<int:pk>/', UserDetailView.as_view()),
  • 无权限访问返回前端数据
    • 普通用户
{
    "detail": "You do not have permission to perform this action."
}

没有权限访问

  • 管理员登录访问返回前端数据
{
    "code": 100,
    "msg": "dream访问删除功能正常",
    "result": ""
}

【四】频率组件

【1】引入

  • 限制访问频次
    • 比如某个接口,一分钟只能访问5次,超过了就得等
    • 按IP地址 限制
    • 按用户id 限制

【2】频率类的使用步骤(固定用法)

(1)创建频次认证类

  • 创建一个自定义的权限认证类,并让它继承自SimpleRateThrottle

(2)重写 get_cache_key 方法

class SimpleRate_Throttle(SimpleRateThrottle):
    # 限制频次
    scope = 3

    # 重写 get_cache_key 方法
    # 返回什么就用什么作为限制条件
    '''
        def get_cache_key(self, request, view):
        """
        Should return a unique cache-key which can be used for throttling.
        Must be overridden.

        May return `None` if the request should not be throttled.
        """
        raise NotImplementedError('.get_cache_key() must be overridden')
    '''
    def get_cache_key(self, request, view):
        # 限制条件:IP地址/用户ID
        # 返回客户端IP地址
        ip = request.META.get('REMOTE_ADDR')
        return ip

(3)写一个类属性自定义命名

# 限制频次
scope = 3

(4)配置文件中配置

  • 这里的变量名要和上面的变量名一致
'DEFAULT_THROTTLE_RATES': {
    'scope': '3/m' # 一分钟访问3次
},

(5)局部使用

class BookView(APIView):
    throttle_classes = [SimpleRate_Throttle]

(6)全局使用

'DEFAULT_THROTTLE_CLASSES': [
	'app01.my_throttling.SimpleRate_Throttle'
],

【3】频率类的使用步骤(固定用法)

(1)创建频次认证类:

  • 首先,需要创建一个频次认证类。
  • 这个类负责限制对某些操作或资源的访问频次。
  • 可以根据具体需求自定义这个类,并继承适合的认证类
    • 比如Django框架中的throttling.BaseThrottle

(2)重写 get_cache_key 方法:

  • 在创建频次认证类后,需要重写其中的get_cache_key方法。
  • 这个方法决定了如何从请求中获取缓存键值,以便在缓存中存储和检索频次信息。

可以考虑使用以下信息作为缓存键值的组成部分:

  • 用户身份:可以使用用户的唯一标识符或者请求中的某些认证信息。
  • 资源标识:如果需要对不同的资源进行频次限制,可以加入资源标识。
  • 操作类型:如果需要对不同的操作类型进行频次限制,可以加入操作类型。

根据具体情况,可以将这些信息组合起来构成一个唯一的缓存键值,并在get_cache_key方法中返回。

(3)写一个类属性自定义命名:

  • 最后,在频次认证类中,可以添加一个类属性来自定义命名。
  • 这个属性可以用于在缓存中存储频次信息时使用。
  • 可以考虑使用以下方式定义类属性:
pythonclass MyThrottle(BaseThrottle):
    cache_name = 'my_custom_throttle'

    # ...
  • 在使用时,可以通过MyThrottle.cache_name来访问这个自定义属性。

(4)配置文件中配置:

  • 配置文件中需要对频次认证类进行相关配置。
  • 具体配置内容包括认证类的名称、参数设置和限制的频次等信息。
  • 例如,在Django框架中,可以在settings.py或其他相关的配置文件中添加如下内容:
pythonREST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': (
        'myapp.apithrottling.MyThrottle',
    ),
    'DEFAULT_THROTTLE_RATES': {
        'my_custom_throttle': '5/minute', # 设置该频次认证类的限制频次为每分钟最多5次请求
    }
}
  • 以上示例中,'myapp.apithrottling.MyThrottle'是频次认证类的名称,my_custom_throttle是前述创建的频次认证类的类属性自定义命名。

(5)局部使用:

  • 如果只想在某些接口或视图函数中使用频次认证类,可以在具体的接口或视图函数中进行配置。
  • 例如,在Django框架中,可以通过装饰器进行局部使用的配置:
pythonfrom myapp.apithrottling import MyThrottle

@throttle_classes([MyThrottle])
def my_view(request):
    # ...

(6)全局使用:

  • 如果希望在整个项目范围内全局使用频次认证类,可以在全局中配置。
  • 例如,在Django框架中,可以在settings.py或其他相关的配置文件中添加如下内容:
pythonREST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': (
        'myapp.apithrottling.MyThrottle',
    ),
    'DEFAULT_THROTTLE_RATES': {
        'my_custom_throttle': '5/minute', # 设置该频次认证类的限制频次为每分钟最多5次请求
    }
}
  • 以上示例中,'myapp.apithrottling.MyThrottle'是频次认证类的名称,my_custom_throttle是前述创建的频次认证类的类属性自定义命名。

【五】权限/认证源码分析

【1】引入

  • 为了能够进行权限和认证的处理,视图类必须继承自APIView类。

    • 只有继承了该类的视图类才能够执行这些操作。
    • 权限/认证
  • 执行流程

    • 在执行流程的dispatch方法中,会触发initial方法

    • 该方法用于在调用方法处理程序之前运行需要进行的操作。

      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)
          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
      
  • 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)
    
  • initial方法中,首先会进行内容协商和接受信息的存储,然后确定API版本(如果正在使用版本控制)。

  • 接下来

    • 会执行身份验证(perform_authentication),以确保传入的请求是经过身份验证的。
    • 然后会执行权限认证(check_permissions),以确保请求具有执行方法所需的权限。
    • 最后,会执行频率限制认证(check_throttles),以确保请求未超过频率限制。

【2】登录认证源码

  • 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
  • 首先,该源码中有一个名为perform_authentication的方法,它是一个用于对传入请求进行身份验证的方法。
  • 在这个方法中,通过调用request.user来触发身份验证。
  • from rest_framework.request import Request
    • Request类的user
# 219 行
@property
def user(self):
    if not hasattr(self, '_user'):
        with wrap_attributeerrors():
            self._authenticate()
    return self._user
  • 接下来,查看了Request类的定义,可以看到user属性被定义为一个实现了身份验证的方法。
  • 在这个方法中,首先判断self对象是否有_user属性
    • 如果没有则调用_authenticate方法进行身份验证。
  • _authenticate方法负责尝试使用每个身份验证实例进行身份验证。
  • Request类的self._authenticate()
def _authenticate(self):
    """
    Attempt to authenticate the request using each authentication instance
    in turn.
    """
    # self.authenticators : 自定义的列表(列表推导式---> [LoginAuth(),])
    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是Request的对象
            self._authenticator = authenticator
            # 解压赋值
            # user:自定义返回的登录认证用户对象
            self.user, self.auth = user_auth_tuple
            return

    self._not_authenticated()
  • _authenticate方法中,使用一个循环遍历自定义的身份验证实例列表authenticators
  • 对于每个身份验证实例,尝试调用其authenticate方法来对传入的请求进行身份验证。
  • 如果抛出了exceptions.APIException异常,则会调用_not_authenticated方法并将异常重新抛出。
  • self.authenticators

    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
        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
    
        if self.parser_context is None:
            self.parser_context = {}
        self.parser_context['request'] = self
        self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET
    
        force_user = getattr(request, '_force_auth_user', None)
        force_token = getattr(request, '_force_auth_token', None)
        if force_user is not None or force_token is not None:
            forced_auth = ForcedAuthentication(force_user, force_token)
            self.authenticators = (forced_auth,)
    
  • 如果authenticate方法返回了一个非空的元组user_auth_tuple,则说明成功进行了身份验证。
  • 此时,会将当前的身份验证实例赋值给_authenticator属性
    • 并将元组中的第一个值赋给user
    • 将第二个值赋给auth
    • 然后结束循环,并返回身份验证成功的结果。
  • 如果循环结束后仍未进行身份验证
    • 则会调用_not_authenticated方法来进行相应处理。
  • Request类的初始化发生在哪?

    • Request类的初始化发生在APIView的dispatch方法中,紧接着是对其他参数的初始化。
  • APIView的 dispatch 前面

    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类的初始化
        request = self.initialize_request(request, *args, **kwargs)
        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
    

Request类的初始化发生在self.initialize_request(request, *args, **kwargs)这一行代码处。

  • 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(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )
    
  • self.initialize_request方法中,会调用Request类的构造函数进行初始化。
  • 同时,Request类的构造函数中会传入一些参数来配置请求的解析器、认证器和内容协商器等。
  • 其中,认证器的实例化是通过self.get_authenticators()方法去获取的,该方法会遍历authentication_classes列表,并实例化每个认证器类。
  • 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]
    
    
  • 总结

    • 认证类:
      • 认证类必须实现一个名为authenticate的方法。
      • 在这个方法中,您可以执行自定义的认证逻辑,例如验证用户凭据、检查令牌等。该方法应返回一个元组或者None。
      • 元组的第一个元素是成功认证的用户对象,第二个元素(可选)是用于进一步授权的令牌或其他必要信息。
    • 认证通过的情况:
      • 如果认证成功,您可以选择返回None或者一个包含认证用户和令牌的元组。
      • 这取决于您的具体需求和实现方式。
      • 根据最佳实践,建议第一个值是表示当前登录用户的对象,第二个值通常是用于后续请求的令牌。
    • 认证失败的情况:
      • 如果认证失败或遇到其他问题,您应该抛出AuthenticationFailed异常。
      • 该异常是继承自Django Rest Framework的APIException,它可以被捕获并采取适当的错误处理措施。

【3】权限认证源码

  • APIView ---> self.check_permissions(request)
def check_permissions(self, request):
    """
        Check if the request should be permitted.
        Raises an appropriate exception if the request is not permitted.
        """
    for permission in self.get_permissions():
        # 权限类的认证:需要重写 has_permission 方法
        # permission 是我们写的权限类的对象
        # self:视图类的对象(AuthorView,BookView)
        if not permission.has_permission(request, self): # 权限没通过
            # 拒绝认证
            self.permission_denied(
                request,
                # 错误信息的指定格式
                message=getattr(permission, 'message', None),
                code=getattr(permission, 'code', None)
            )
  • APIView中的check_permissions方法:
    • 这个方法在APIView类中,用于检查请求是否应该被允许。
    • 它会依次遍历通过get_permissions方法获取的权限类列表,并调用每个权限类的has_permission方法来进行认证。
    • 如果其中任何一个权限类认证失败(has_permission返回False),则会引发适当的异常来拒绝请求。
  • check_permissions方法中的权限认证流程:
    • 首先,获取视图类中配置的权限类列表,通过调用self.get_permissions()来实现。
    • self.get_permissions()方法会实例化权限类列表中的每个权限类对象,并返回一个包含这些对象的列表。
    • check_permissions方法中,遍历这个权限对象列表,并对每个权限对象调用has_permission(request, self)方法进行具体的认证判断。
    • 如果某个权限对象的has_permission方法认证失败(返回False),则调用self.permission_denied方法拒绝认证,并传递相应的错误信息(如错误消息和错误代码)。
  • APIView ---> self.get_permissions()方法

    • self.get_permissions() ----> [AdminPermission(),]
    def get_permissions(self):
        """
        Instantiates and returns the list of permissions that this view requires.
        """
        return [permission() for permission in self.permission_classes]
    
    • permission_classes:配置在视图类中的permission_classes
  • permission_classes属性:
    • 在视图类中,您可以配置一个permission_classes属性,用于指定需要应用的权限类列表。
    • get_permissions方法中,会遍历permission_classes列表,并实例化每个权限类,将实例化后的权限类对象作为列表的元素返回。

【4】频次认证源码

    def check_throttles(self, request):
        """
        Check if request should be throttled.
        Raises an appropriate exception if the request is throttled.
        """
        throttle_durations = []
        for throttle in self.get_throttles():
            if not throttle.allow_request(request, self):
                throttle_durations.append(throttle.wait())

        if throttle_durations:
            # Filter out `None` values which may happen in case of config / rate
            # changes, see #1438
            durations = [
                duration for duration in throttle_durations
                if duration is not None
            ]

            duration = max(durations, default=None)
            self.throttled(request, duration)
  • check_throttles 方法:这个方法用于检查请求是否需要进行频次限制(throttling)。在视图的处理过程中,会在适当的时机调用该方法。如果请求受到频次限制,则会引发适当的异常来阻止请求继续处理。

  • check_throttles 方法中的频次认证流程:

    • 首先,创建一个空的 throttle_durations 列表,用于存储每个throttle对象返回的限制时间。
    • 通过调用 self.get_throttles() 方法获取限制对象列表。
    • 遍历限制对象列表,对每个限制对象调用 allow_request(request, self) 方法来检查请求是否允许通过此限制。
      • 如果请求不允许通过限制(allow_request方法返回 False),则调用 throttle.wait() 方法获取限制的时间间隔,并将其添加到 throttle_durations 列表中。
    • 如果存在 throttle_durations 列表,则说明请求被至少一个限制所影响。
      • 在此列表中过滤掉 None 值(可能出现在配置或速率更改的情况下),然后找到列表中的最大值作为限制的持续时间。
      • 调用 self.throttled(request, duration) 方法,将请求和限制的持续时间传递给 throttled 方法,从而引发一个表示请求被频次限制的异常。

【补充】Django中的翻译函数

# 只要做了国际化,会自动翻译成,当前国家的语言
from django.utils.translation import gettext_lazy as _
# 使用方法 _是一个函数,函数调用,可以将参数翻译成其他语言
_('hello')
  • django.utils.translation

    • 这是Django框架提供的翻译模块,用于处理多语言支持和翻译。
  • gettext_lazy() 函数:

    • 这是一个延迟加载版本的翻译函数,用于在运行时动态地翻译字符串。

    • 它接受一个字符串作为参数,并返回一个被翻译后的字符串对象。

  • _('hello')

    • 这是一个使用了 gettext_lazy() 函数的例子,将字符串 'hello' 进行翻译。
    • 通过在字符串前加上 _,Django会自动将其翻译成当前国家的语言。
    • 注意:
      • 需要先在Django项目中做国际化配置,包括指定可用的语言、提供相应的翻译文件等。
      • 只有在正确配置了国际化后,翻译才能正常工作。
  • 使用翻译函数 gettext_lazy()gettext() 可以方便地支持多语言。

  • 通过对需要翻译的字符串进行包装,Django将根据当前语言环境自动选择合适的翻译文本来替换原始字符串,从而实现页面内容的国际化。