DRF 三大认证

三大认证使用

【0】准备

(1)APIView源码回顾

  • APIView不用csrf验证了
  • APIView的request方法不在是以前的request方法,有了很多好用的新方法
  • APIView内部还分别进行了了用户、权限、频率的验证
  • 三大认证之后才执行的视图层的方法

image-20240417144150517

(2)模型表创建

from django.db import models

# 图书表
class Book(models.Model):
    name = models.CharField(max_length=64, verbose_name="书名")
    price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="价格")
    publish_name = models.CharField(max_length=64, verbose_name="出版社名字")

# 用户表
class User(models.Model):
    name = models.CharField(max_length=64, verbose_name='用户名')
    password = models.CharField(max_length=64, verbose_name='密码')
    role = models.IntegerField(choices=((0, "Admin"), (1, 'Normal')), verbose_name='用户名')

# 用户token表
class UserToken(models.Model):
    token = models.CharField(max_length=64, verbose_name='token')
    user = models.OneToOneField(to=User, on_delete=models.CASCADE, verbose_name="关联用户表")

【1】认证组件

(1)简介

  • 三大认证之首,只有通过这个认证才会进行两外两个认证
  • 认证组件用于确定请求是来自谁。例如,它可以检查一个请求是否附带了一个有效的用户会话token,从而识别出请求的用户。

(2)使用方法

  1. 编写一个继承BaseAuthentication的类

    • from rest_framework.authentication import BaseAuthentication
  2. 重写authenticate方法

    • 可以在BaseAuthentication类中看到authenticate() must be overridden的提示,提示我们要重写
  3. 重写方法中进行特殊内容的判断(这里是token)

    • token是后端开发者对前端传输数据进行要求的,可以指定内容,可以指定格式,由于无论什么请求方式,都会发送请求头,所以可以指定数据放在请求头中
    • 请求通过,直接返回用户对象和token
    • 请求失败,抛异常AuthenticationFailed
      • from rest_framework.exceptions import AuthenticationFailed
      • 例如:raise AuthenticationFailed("请先登录再重试。")
  4. 配置使用

    1. 局部使用

      • 在试图内中添加authentication_classes = [自定义认证类列表],这是个列表,所以说明可以添加多个认证类
    2. 全局使用

      • 直接去drfsetting配置文件中找

      • REST_FRAMEWORK = {
            'DEFAULT_AUTHENTICATION_CLASSES': [
                '字符串形式导入自定义认证类',
            ],
        }
        
    3. 局部禁用

      • 再进行全局配置即所有视图类都需要登录认证以后
      • 根据就近优先原则,只要将当前的视图类中authentication_classes 置空就可以取消认证,即局部禁用

(3)示例

  • 路由层:使用继承ViewSetMixin的视图类,所以直接自动生成路由
from django.urls import path, include
from .views import UserAPIView
from rest_framework.routers import SimpleRouter

router = SimpleRouter()
router.register(prefix='user', viewset=UserAPIView, basename='user')
router.register(prefix='book', viewset=BookAPIView, basename='book')
urlpatterns = [
    path('', include(router.urls))
]
  • 视图函数登录功能
    • 注册就直接再数据库添加了
    • update_or_create(defaults={'token': token}, user=user_obj)讲解
      • defaults参数是一个字典,需要添加的所有字段信息(除了后面的那个字段)
      • 这里的字段是user,即根据user进行判断,这个数据是已经添加过的还是新的,如果是新的那么就是创建,如果是旧的数据,那么就进行更新
# 登录功能
import uuid
from rest_framework.viewsets import ViewSet
from .models import User, UserToken
from rest_framework.response import Response
from rest_framework.decorators import action


class UserAPIView(ViewSet):
    @action(methods=['post'], detail=False)
    def login(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        user_obj = User.objects.filter(username=username, password=password).first()
        if user_obj:
            token = uuid.uuid4()
            UserToken.objects.update_or_create(defaults={'token': token}, user=user_obj)
            return Response({"code": 100, "msg": "登录成功", "token": token})
        return Response({"code": 101, "msg": "登录失败,用户名或密码错误"})

# 书籍查询所有,添加单本
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin, CreateModelMixin
from .serializer import BookModelSerializer
from .authentication import LoginAuth

class BookAPIView(GenericViewSet, ListModelMixin, CreateModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookModelSerializer
    # 认证类
    authentication_classes = [LoginAuth]

# 序列化类
from rest_framework.serializers import ModelSerializer
from .models import Book

class BookModelSerializer(ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'
  • 认证类编写
    • 需要注意的是,前端的数据是放在请求头中的token,再后端需要加上HTTP_大写变量取值
    • 最后成功返回usertoken
    • 不成功抛出异常AuthenticationFailed
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from .models import UserToken


class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        token = request.META.get("HTTP_TOKEN")
        token_obj = UserToken.objects.filter(token=token).first()
        if token_obj:
            return token_obj.user, token
        raise AuthenticationFailed("请先登录再重试。")
  • 试图函数测试:书籍信息查询功能
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin
from .serializer import BookModelSerializer
from .authentication import LoginAuth

class BookAPIView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookModelSerializer
    authentication_classes = [LoginAuth]
  • 不登陆测试

image-20240417154617946

  • 登录后添加token测试

image-20240417155235666

【2】权限组件

(1)简介

  • 三大认证的第二个,到这里说明已经通过了认证组件
  • 权限组件用于确定已认证的用户是否有权限执行某个操作。例如,一个API视图可能只允许特定的用户组或具有特定权限的用户访问。

(2)使用方法

  1. 编写一个继承BasePermission的类

    • from rest_framework.permissions import BasePermission
  2. 重写has_permission方法

    • 根据自定义规则,通过返回True,失败返回False
    • 验证不通过自定义返回信息f方法,抛出异常AuthenticationFailed
      • from rest_framework.exceptions import PermissionDenied
      • 例如:raise PermissionDenied(f'你是【{role_info}】,没有权限执行当前操作。')
  3. 配置使用

    1. 局部使用

      • 在试图内中添加permission_classes = [自定义权限类列表],这是个列表,所以说明可以添加多个权限类
    2. 全局使用

      • 直接去drfsetting配置文件中找

      • REST_FRAMEWORK = {
            'DEFAULT_PERMISSION_CLASSES': [
                '字符串形式导入自定义认证类',
            ],
        }
        
    3. 局部禁用

      • 再进行全局配置即所有视图类都需权限认证以后
      • 根据就近优先原则,只要将当前的视图类中permission_classes 置空就可以取消权限认证,即局部禁用

(3)示例

  • 注意:前面必须要有认证类通过认证才可以即一定要有authentication_classes,因为这里的权限认证是从登录后的信息拿取权限的内容

  • 权限类编写

    • 根据自定义规则进项权限检查
    • 最后成功返回True
    • 不成功抛出异常PermissionDenied
    • 由于User表是自定义的没有继承django的用户表,所以是没有is_authenticated方法,需要再模型表中自定义,返回个True即可,表示是这个表创建的
# 权限
from rest_framework.permissions import BasePermission
from rest_framework.exceptions import PermissionDenied

class AdminPermission(BasePermission):
    def has_permission(self, request, view):
        # 检查是否登录
        if request.user.is_authenticated:
            # 检查权限只有管理员才可以
            if request.user.role == 0:
                return True
            role_info = request.user.get_role_display()
            raise PermissionDenied(f'你是【{role_info}】,没有权限执行当前操作。')
        raise PermissionDenied(f'请先登录执行当前操作。')
        
# 模型表
class User(models.Model):
    username = models.CharField(max_length=64, verbose_name='用户名')
    password = models.CharField(max_length=64, verbose_name='密码')
    role = models.IntegerField(choices=((0, "Admin"), (1, 'Normal')), verbose_name='用户名')

    @property
    def is_authenticated(self):
        return True
  • 视图函数测试:
    • 在添加一个书籍对单本图书的视图函数,使用路由注册视图集时,每个视图集通常应该对应一个特定的 URL 前缀,以避免冲突。
# 路由层
from django.urls import path, include
from .views import UserAPIView, BookAPIView, BookOneAPIView
from rest_framework.routers import SimpleRouter

router = SimpleRouter()
router.register(prefix='user', viewset=UserAPIView, basename='user')
router.register(prefix='books', viewset=BookAPIView, basename='books')
router.register(prefix='book', viewset=BookOneAPIView, basename='book_one')
urlpatterns = [
    path('', include(router.urls))
]

# 视图层
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, CreateModelMixin, UpdateModelMixin, \
    DestroyModelMixin
from .serializer import BookModelSerializer
from .authentication import LoginAuth
from .permission import AdminPermission

class BookAPIView(GenericViewSet, ListModelMixin, RetrieveModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookModelSerializer
    authentication_classes = [LoginAuth]
    permission_classes = []

class BookOneAPIView(GenericViewSet, CreateModelMixin, UpdateModelMixin, DestroyModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookModelSerializer
    authentication_classes = [LoginAuth]
    permission_classes = [AdminPermission]
  • 普通用户测试

image-20240417185503126

  • 管理员测试

image-20240417185547653

【3】频率组件

(1)简介

  • 三大认证的最后一个,到这里说明已经通过了认证和权限组件
  • 权限组件用于确定已认证的用户是否有权限执行某个操作。例如,一个API视图可能只允许特定的用户组或具有特定权限的用户访问。

(2)SimpleRateThrottle使用方法

  1. 编写一个继承SimpleRateThrottle的类

    • from rest_framework.throttlingimport SimpleRateThrottle
  2. 重写get_cache_key方法

    • 可以在SimpleRateThrottle类中看到get_cache_key() must be overridden的提示,提示我们要重写
    • 在类属性中定义一个rate属性
      • 参数形式为:3/m,这里表示一分钟内最多访问三次
      • 所以第一个参数是次数,/后面的是单位,s是秒,m是分钟,h是小时,实际上写全称也可以,只和首字母有关,源码导致
    • 这个方法需要返会限制频率的唯一标识
      • 返回request.META.get('REMOTE_ADDR')表示IP
  3. 配置使用

    1. 局部使用

      • 在试图内中添加throttle_classes= [自定频率类列表],这是个列表,所以说明可以添加多个频率类
    2. 全局使用

      • 直接去drfsetting配置文件中找

      • REST_FRAMEWORK = {
            'DEFAULT_THROTTLE_CLASSES': [
                '字符串形式导入自定义频率类',
            ],
        }
        
    3. 局部禁用

      • 再进行全局配置即所有视图类都限制频率以后
      • 根据就近优先原则,只要将当前的视图类中throttle_classes 置空就可以取消频率限制,即局部禁用

(3)示例

  • 频率类编写
    • 重写get_cache_key方法,返回IP
    • 添加类属性rate
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle

class CommonThrottle(SimpleRateThrottle):
    rate = '3/m'

    def get_cache_key(self, request, view):
        return request.META.get('REMOTE_ADDR')
  • 视图函数测试:
class BookAPIView(ReadOnlyModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookModelSerializer
    authentication_classes = [LoginAuth]
    permission_classes = []
    throttle_classes = [CommonThrottle]

image-20240417191537786

(4)BaseThrottle使用方法

  • SimpleRateThrottle的使用方法差不多,不过要麻烦很多,几乎要完全自定义,需要重写allow_request方法

  • 补充一点:

    • 正常来说,通过返回True,不通过返回False
    • 但是不通过可以自定义返回信息,使用Throttled实例化一个信息,并抛出这个异常。
      • from rest_framework.exceptions import Throttled
from rest_framework.throttling import BaseThrottle
from rest_framework.exceptions import Throttled

class ComplexThrottle(BaseThrottle):
    login_dict = {}  # 登录信息字典
    frequency = 3  # 时间内访问次数
    interval = 10  # 时间间隔 s

    def allow_request(self, request, view):
        print(self.login_dict)
        now_ip = request.META.get('REMOTE_ADDR')
        now_time = time.time()
        # 是否是新用户
        if now_ip not in self.login_dict.keys():
            self.login_dict[now_ip] = [now_time]
            return True
        else:
            # 时间间隔
            dif = now_time - self.login_dict.get(now_ip)[0]
            # 时间间隔大于指定间隔
            if dif > self.interval:
                self.login_dict.get(now_ip).pop(0)
                self.login_dict.get(now_ip).append(now_time)
                return True
            # 存储超过指定数量不在添加
            elif len(self.login_dict.get(now_ip)) >= self.frequency:
                # 自定义返回内容
                raise Throttled(detail={'msg': f"访问频率过高,请等待{self.interval - int(dif)}秒后再试。"})
            # 最后说明:在时间间隔内,且有位置可以添加
            else:
                self.login_dict[now_ip].append(now_time)
                return True

相关推荐

  1. django的DRF()

    2024-04-24 10:28:01       22 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-24 10:28:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-24 10:28:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-24 10:28:01       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-24 10:28:01       20 阅读

热门阅读

  1. Linux CentOS 7 服务器集群硬件常用查看命令

    2024-04-24 10:28:01       12 阅读
  2. Quest 2 VR程序读取本地图片

    2024-04-24 10:28:01       12 阅读
  3. 标准化,信息化,数字化,智能化

    2024-04-24 10:28:01       14 阅读
  4. 在微信小程序部署AI模型的几种方法

    2024-04-24 10:28:01       14 阅读
  5. C# 异步编程

    2024-04-24 10:28:01       14 阅读
  6. python实现读取,修改excel数据

    2024-04-24 10:28:01       15 阅读
  7. 等保测评试题分享(4)

    2024-04-24 10:28:01       17 阅读
  8. Python dlib(HOG+SVM)人脸识别总结

    2024-04-24 10:28:01       15 阅读
  9. 组合模式(Composite)

    2024-04-24 10:28:01       15 阅读
  10. nn.Sequential与tensorflow的Sequential对比

    2024-04-24 10:28:01       47 阅读