DRF的三大认证

【 零 】一些注意事项

【1】如何使用视图类

image-20240417152417982

#1 APIView 
	-如果后续,写个接口,不需要用序列化类,不跟数据库打交道
    	-发送短信接口,发送邮件接口
    
#2 GenericAPIView
	-如果后续,写个接口,要做序列化或跟数据库打交道,就要继承他
    	-登陆接口,注册接口
        
#3 5个视图扩展类,必须配合 GenericAPIView
	-5个接口或之一
    -class PublishView(GenericAPIView,ListModelMixin)
    	queryset
        serializer_class
        def get(request):
            return super().list(request)
        
        
# 4 如果要写5个接口之一或之几,直接使用9个视图子类
	-新增
    -class BookView(CreateAPIView):
        queryset
        serializer_class
        
        
# 6  ViewSet: 原来继承APIView,但是想自动生成路由或路由映射,就继承他
	-send_sms

# 7 GenericViewSet:原来继承GenericAPIView,但是想自动生成路由或路由映射,就继承他
	-login
    -register
    
# 8 ModelViewSet-->5个接口都写,自动生成路由


# 9 扩展写法
	class BooView(GenericViewSet,ListModelMixin):
    class BooView(ViewSetMixin,GenericAPIView,ListModelMixin):
	class BooView(ViewSetMixin,ListAPIView):
		queryset
        serializer_class
        def login():

小结

  1. APIView
    • 适合于不需要序列化类或与数据库交互的接口,比如发送短信接口、发送邮件接口等。
    • 如果接口不需要序列化或数据库交互,直接继承APIView即可。
  2. GenericAPIView
    • 适合于需要序列化或与数据库交互的接口,比如登录接口、注册接口等。
    • 如果接口需要序列化或数据库交互,就要继承GenericAPIView。
  3. 视图扩展类
    • 这些扩展类提供了一些额外的功能,如过滤、排序等,需要配合GenericAPIView使用。
    • 示例中的PublishView是一个很好的例子,使用ListModelMixin扩展了通用视图的功能,实现了列表查询的接口。
  4. 直接使用视图子类
    • 如果你知道接口肯定需要序列化或数据库交互,可以直接使用视图子类,如CreateAPIView
    • 这样可以简化代码,避免重复定义querysetserializer_class等属性。
  5. ViewSetGenericViewSet
    • 这两个类都是用来自动生成路由或路由映射的。
    • 如果你想要自动生成路由或路由映射,就应该使用这两个类。
    • ViewSet通常用于自定义动作,而GenericViewSet通常用于基于通用操作的视图。
  6. ModelViewSet
    • 继承自GenericViewSet,但提供了一组默认的CRUD操作。
    • 如果你的接口需要实现标准的CRUD功能,并且与数据库交互,可以直接使用ModelViewSet

【 2 】模块与包的注意事项

【 1 】什么模块

模块

  • 模块是包含Python代码的文件。这些文件可以包含Python语句、函数、类等。
  • 每个Python源文件都可以被视为一个模块。
  • 其他Python程序可以通过import语句导入模块,并使用模块中定义的函数、类等。
'''
一个py 文件,导入使用,他就是模块
一个py,点右键运行,他叫 脚本文件
'''

【 2 】什么包

'''
一个文件夹下 有 __init__.py ,下面又有很多文件夹和py文件,导入使用的 '''
  • 包是一个包含多个模块的目录,它通常还包含一个特殊的__init__.py文件,用于标识该目录为一个包。
  • 包的目录结构可以是嵌套的,允许创建更复杂的模块组织结构。
  • 包允许将相关的模块组织在一起,并提供了更好的命名空间管理。
  1. 模块导入
    • 使用 import 语句导入模块时,可以选择导入整个模块或者只导入其中的部分内容。
    • 也可以使用 from module import name 的形式导入特定的对象或函数。
  2. 模块命名
    • 建议模块的命名符合 PEP 8 的命名规范,即小写字母加下划线(snake_case)。
    • 避免使用 Python 内置的模块名或常用的模块名,以免产生命名冲突。
  3. 包结构
    • 包是由多个模块组成的目录,其中必须包含一个 __init__.py 文件来标识该目录为一个包。
    • 包可以有多层次的结构,可以嵌套使用。
  4. __init__.py 文件
    • __init__.py 文件可以为空,也可以包含包的初始化代码。
    • __init__.py 中可以定义 __all__ 变量,指定在使用 from package import * 时导入的模块列表。
  5. 模块搜索路径
    • Python 解释器会在一系列的目录中搜索要导入的模块,包括当前目录、内置模块目录和 sys.path 中指定的路径。
  6. 包相对导入
    • 在包内部的模块中可以使用相对导入来引用同一包下的其他模块。
    • 相对导入使用 from .module import name 的形式,其中 . 表示当前包。
  7. 循环导入
    • 需要注意避免循环导入,即 A 模块导入了 B 模块,而 B 模块又导入了 A 模块。
    • 循环导入会导致程序无法正常执行,通常应该重构代码以避免这种情况。
  8. 包的命名空间
    • 包提供了一种命名空间的机制,可以避免不同模块之间的命名冲突。
    • 在包内部,每个模块都拥有自己的命名空间,模块中定义的变量、函数和类默认只在模块内可见。
# 以后只要看到这个报错
	ModuleNotFoundError: No module named 'xx' ,但是这个模块有
    
    导入的问题:路径不对
    
坑,如果py文件中使用了相对导入,他就不能右键运行了

【 3 】修改项目名字

image-20240417154838459

# 1 文件夹改名
	-别的进程打开了---》重启电脑改文件夹名
# 2  改项目名
	项目路径下  .idea-->项目配置
    如果打开项目有问题--》把 .idea删除--》再打开
    
# 3 项目中某个文件件名---》慎改
	-模块路径都定好了
    -一旦改了--》项目百分比用不了
    -在项目上邮件---》replace--》项目中所有叫原来文件夹名的 字符串
    
# 4 django项目运行不了
	1 django-server删除重新创建
    2 setting中搜django--》配置项目路径和配置文件路径

【 4 】断言

image-20240417154310594

# 源码中经常见到断言
	
# 语法:
assert 条件,'字符串'
assert condition, message
'''
condition 是一个要检查的条件表达式,如果该条件为 False,则会引发 AssertionError。
message 是可选的,用于在引发异常时提供错误消息。
'''

# 翻译成if
if 条件:
    条件成立,继续走后续代码
else:
    raise Exception('字符串')
    
    
# 案例
assert isinstance(a,int),'不行,a必须是int'

# 注意
	在 Python 中,assert 语句的实现是作为一个语法结构而不是一个函数,因此它的源码不像内置函数那样可以直接查看。assert 的行为实际上是由 Python 解释器的实现来处理的。

# 源码中
assert isinstance(request, HttpRequest), (
        'The `request` argument must be an instance of '
        '`django.http.HttpRequest`, not `{}.{}`.'
        .format(request.__class__.__module__, request.__class__.__name__)
    )

【一】前期的准备工作

  • 登录接口: 首先写一个登录接口,这个接口返回token,下一次请求浏览器只要带着token过来,就是说明用户登录了,不带token,就说明没有登录。

  • 对于post请求 —> token可以放请求体

  • 对于get 请求 —> get请求没有请求体,token可以放在请求首行的地址栏

  • 其实, 一般token放在请求头上传到后端。

前端传入的数据从哪里取?

【 1 】post请求

  • 对于post请求中携带的token: 原生django: request.body request.POST Django drf: request.data

【 2 】get请求

  • 对于get请求中携带的token: 原生django: request.get Django drf: request.query_params

image-20240417155235765

# 三大认证之认证
	-登陆认证
    -用户登陆成功--》签发token
    -以后需要登陆才能访问的接口,必须写到当时登陆签发的token过来
    -后端验证--》验证通过--》继续后续操作
    
    
# 登陆接口
	-Userspuer
    -UserToken---》存用户登陆状态【作用等同于session表】
    	-跟Userspuer表是一对一
     
# 

【 3 】登陆注册接口的实现

  • 序列化类

class SileBookSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id','name']


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['username','password']
        extra_kwargs = {
            # 确保密码只在创建时输入,而不是在序列化输出中显示
            'password':{'write_only':True}
        }

    def create(self,validated_data):
        user = User.objects.create(**validated_data)
        return user

    def update(self, instance, validated_data):
        instance.username = validated_data.get('username', instance.username)
        password = validated_data.get('password')
        if password:
            # 如果提供了新密码,则更新密码
            instance.set_password(password)
        instance.save()
        return instance
  • views.py

from rest_framework.decorators import action
from rest_framework.viewsets import ViewSet
from two.serial import BookSerializer, SileBookSerializer,UserSerializer
from rest_framework.response import Response
from two.models import Book2, User, UserToken
import uuid


class UsersViews(ViewSet):
    @action(methods=['GET'], detail=False)
 	# http://127.0.0.1:8200/two/users/login/
    def login(self, request):
        # 用户名、密码 ---> request.data
        username = request.data.get('username')
        password = request.data.get('password')
        user = User.objects.filter(username=username,
                                   password=password).first()
        if user:
            token = str(uuid.uuid4())
            UserToken.objects.update_or_create(defaults={'token': token}, user=user)
            print(token)
            # 登录成功 --> 生成随机字符串: token---> 存到UserToken表中
            return Response({'code': 200, 'msg': '登录成功', 'token': token})
        else:
            return Response({'code': 404, 'msg': '用户名或者密码错误!!!'})

    @action(methods=['POST'], detail=False)
    #http://127.0.0.1:8200/two/users/register/
    @action(methods=['POST','PUT'], detail=False)
    def register(self, request):
        serializer = UserSerializer(data=request.data)
        if serializer.is_valid():
            username = serializer.validated_data.get('username')
            if User.objects.filter(username=username).exists():
                return Response({'code': 403, 'msg': '用户已存在', 'res': serializer.data})
            else:
                serializer.save()
                return Response({'code': 200, 'msg': '注册成功', 'res': serializer.data})
        else:
            return Response({'code': 404, 'msg': '注册失败', 'res': serializer.errors})
  • urls.py

from two.views import BookView, PublishView, Userview ,UsersViews # ,BookDetailView
# 自动生成路由
from rest_framework.routers import SimpleRouter, DefaultRouter
from django.urls import path,include
# 2 实例化得到对象
# router = SimpleRouter()
router = DefaultRouter()
# 3 执行对象的方法
# 这个方法就是自动帮我们生成映射  {'get': 'list', 'post': 'create'}
router.register('user', Userview, 'user')

router.register('users', UsersViews, 'users')
# router.register('books', BookView, 'books')


# 4 对象.属性 拿到值
print(router.urls)

# [<URLPattern '^books/$' [name='books-list']>, <URLPattern '^books/(?P<pk>[^/.]+)/$' [name='books-detail']>]
urlpatterns = [
    # path('request/', ReqsuetView.as_view()),
    #  什么是映射下面这种就是
    path('publish/', PublishView.as_view({'get': 'login'})),

    # 前面可以再加前缀
    path('api/v1/',include(router.urls))
]
# 5 把自动生成的路由,放到 urlpatterns中
urlpatterns += router.urls

image-20240416210336845

【 二 】配置认证类(重要)

将我们写好的认证类添加到authentications_classes类属性。

在写认证文件的时候最好单独创建一个文件夹

  • 认证作用:校验用户是否登录,如果登录了,继续往后走,如果没有登录,直接返回
from rest_framework.authentication import BaseAuthentication
from .models import UserToken
from rest_framework.exceptions import AuthenticationFailed
'''
1 写个类,继承BaseAuthentication
2 重写 authentication 方法
3 在authentication 中完成用户登陆的校验
    -用户携带token--》能查到,就是登陆了
    -用户没带,或查不到,就是没登录
4 如果校验通过,返回当前登录用户和token
5 如果没校验通过,抛AuthenticationFailed
'''
class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        # request 是新的request,从request中取出用户携带的token
        # 用户带在哪了? 后端定的:能放那? 请求地址 ?token   请求体中  请求头中
        # 要求放在请求头中
        token=request.META.get('HTTP_TOKEN')
        # 校验
        user_token=UserToken.objects.filter(token=token).first()
        if user_token:
            # 拿到他是谁,当前登录用户
            user=user_token.user
            return user,token
        else:
            raise AuthenticationFailed('很抱歉,您没有登陆,不能操作')
            
       

如果没有进行验证就会出现下面的情况

image-20240417132901791

# views.py
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, DestroyModelMixin, ListModelMixin, UpdateModelMixin
from rest_framework.viewsets import GenericViewSet
from .serial import BookSerializer
from .models import Book2

class BookView(GenericViewSet, ListModelMixin, CreateModelMixin, DestroyModelMixin):
    # 配置认证类为LoginAuth
    authentication_classes = [LoginAuth]

    queryset = Book2.objects.all()
    serializer_class = BookSerializer

跟上面的登陆接口配合要先登录才能查询 这个就是认证的·作用

image-20240417133741128

我们只需要在登陆的接口页面复制我们的"token": "ba311041-a61a-4253-8770-ff4bb5d2335b"到books接口就可以了

image-20240417133710110

【 1 】全局配置

two 是你的应用程序,authaction 是你自定义的文件名,而 LoginAuth 是你在其中定义的类名。

通常情况下,你应该确保以下几点:

  1. 在你的应用程序 two 中创建一个名为 authaction 的模块(即文件夹),用于存放自定义的身份验证类文件。
  2. authaction 模块中创建一个 Python 文件,例如 auth.py,用于定义你的自定义身份验证类。
  3. auth.py 文件中定义 LoginAuth 类,该类应该继承自 rest_framework.authentication.BaseAuthentication,并实现 authenticate 方法来执行身份验证逻辑。
  4. 在 Django REST Framework 的配置中,将 DEFAULT_AUTHENTICATION_CLASSES 设置为包含你的自定义身份验证类的路径字符串,确保框架能够找到并使用你的身份验证类。

以下是一个简单的示例:

# 文件路径:two/authaction/auth.py

from rest_framework.authentication import BaseAuthentication

class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        # 在这里执行身份验证逻辑,例如检查请求中的身份验证信息,并返回认证结果
        # 如果认证成功,返回 (user, auth),否则返回 None
        pass  # 这里需要实现你的身份验证逻辑

然后,在你的 Django 配置文件中,将 DEFAULT_AUTHENTICATION_CLASSES 设置为你的自定义身份验证类的路径,例如:

# settings.py

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'two.auth.LoginAuth',
        # 其他默认的身份验证类...
    ],
    # 其他设置...
}

【 2 】局部配置与禁用

  • 视图类配置authentication_classes = [LoginAuth]

from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, DestroyModelMixin, ListModelMixin, \
    UpdateModelMixin
from rest_framework.viewsets import GenericViewSet
from .serial import BookSerializer
from .models import Book2
from .authaction import LoginAuth


class BookView(GenericViewSet, ListModelMixin, CreateModelMixin, DestroyModelMixin):
    # 配置认证类为LoginAuth
    authentication_classes = [LoginAuth]

    queryset = Book2.objects.all()
    serializer_class = BookSerializer

  • 局部禁用authentication_classes = []

image-20240417152134490

【 3 】认证组件使用步骤

# 1 写一个认证类,继承BaseAuthentication
# 2 重写authenticate方法,在该方法在中实现登录认证:token在哪带的?如果认证它是登录了
# 3 如果认证成功,返回两个值【返回None或两个值】
# 4 认证不通过,抛异常AuthenticationFailed
# 5 局部使用和全局使用
	-局部:只在某个视图类中使用【当前视图类管理的所有接口】
        class BookDetailView(ViewSetMixin, RetrieveAPIView):
            authentication_classes = [LoginAuth] 
    -全局:全局所有接口都生效(登录接口不要)
    REST_FRAMEWORK = {
    	'DEFAULT_AUTHENTICATION_CLASSES':['two.authenticate.LoginAuth']
        '''应用程序.文件名(authenticate).函数名'''
	}
    
    -局部禁用:
    	 class BookDetailView(ViewSetMixin, RetrieveAPIView):
            authentication_classes = [] 

'''
重写和重载
重写:方法名和参数一样。
重载:方法名一样,参数不一样。
'''

【补充】(了解即可)

  • 把我们写好的认证类添加到action装饰器的authentication_classes参数中.

image-20240417161447904

class UsersViews(ViewSet):
    # authentication_classes = []
    @action(methods=['GET'], detail=False,authentication_classes=[LoginAuth])
    def login(self, request):

        # 用户名、密码 ---> request.data
        username = request.data.get('username')
        password = request.data.get('password')

        user = Usersuper.objects.get(username=username)
        if check_password(password, user.password):  # 检查用户输入的密码是否与数据库中存储的密码匹配
            token = str(uuid.uuid4())
            UserToken.objects.update_or_create(defaults={'token': token}, user=user)
            # 登录成功 --> 生成随机字符串: token---> 存到UserToken表中
            return Response({'code': 200, 'msg': '登录成功', 'token': token})
        else:
            return Response({'code': 404, 'msg': '用户名或者密码错误!!!'})

小结

	-1 登录认证---》有的接口需要登陆后才能访问
    -2 登录接口:自定义用户表,UserToken表[用户登录信息存在后端--》本质就是django-session]
    	-cookie和session区别?
        -登录成功--》返回给前端随机字符串
        -目的:后期它再访问,要携带字符串---》我们通过校验--》确认是我们给的,在UserToken表中有记录
     -3 如何编写认证类
    	1 写个类,继承BaseAuthentication
        2 重写 authenticate
        3 在authenticate 内完成登录认证
        	-只要携带了我们给的字符串---》数据库中能查到--》才是登录
            -校验通过,返回两个值:当前登录用户,token
            -校验失败,抛异常
            
     -4 如何使用
    	-1 视图类中配置--局部使用
        -2 全局配置---》全局都生效
        -3 局部禁用

【 三 】权限控制(重要)

【 0 】前言

权限相关说明:

  • 登录接口需要禁用权限,查询所有接口也需要禁用权限。

  • 权限并不是这么简单,权限类的逻辑可以很复杂。用表保存用户的权限,当请求来了去权限表获取单个用户的权限.

  • 注意,权限和认证都可以配置多个

image-20240417172843638

  • 注意 | 权限是在认证之后执行的

# APIView --->dispatch--》APIView的initial---》APIView的三个方法
    self.perform_authentication(request) # 认证
    self.check_permissions(request)# 权限
    self.check_throttles(request)# 频率

APIView 的关键方法和流程

  1. dispatch 方法
    • dispatch 方法是 Django REST Framework 中视图类的核心方法之一。它负责将请求分发给适当的处理方法(如 getpostput 等)。
    • dispatch 方法中,通常会调用 initial 方法进行初始化,然后根据请求方法(GET、POST 等)调用对应的处理方法。
  2. initial 方法
    • initial 方法在 dispatch 方法中被调用,用于执行视图的一些初始化操作。
    • 其中包括调用 perform_authentication 进行认证、check_permissions 进行权限检查以及 check_throttles 进行频率限制检查等。
  3. perform_authentication 方法
    • perform_authentication 方法用于执行认证操作,通常在视图处理请求之前调用。
    • 在这个方法中,可以根据项目的认证机制对请求进行认证,例如基于 Token、Session、OAuth 等认证方式。
  4. check_permissions 方法
    • check_permissions 方法用于检查请求是否具有访问权限,通常在认证之后调用。
    • 在这个方法中,可以根据用户的权限或角色来决定是否允许该请求的访问。
  5. check_throttles 方法
    • check_throttles 方法用于检查请求的频率限制,防止恶意或过多的请求。
    • 在这个方法中,可以根据项目的需求来设置请求的频率限制,例如每秒最多几次请求等。

​ 其中 initial 方法会在 dispatch 中被调用,而 perform_authenticationcheck_permissionscheck_throttles 这三个方法会在 initial 中被依次调用,用于认证、权限检查和频率限制。

【 1 】准备工作

# Django 自带的权限系统
class User(models.Model):
    username = models.CharField(max_length=64)
    password = models.CharField(max_length=64)
    user_type = models.IntegerField(choices=((1,'注册用户'),(2,'普通管理员'),(3,'超级管理员')),default=1)
  • 然后就是最好创建一个py文件 我这里命名为permissions.py
  • 下面就是一个简单的定义

​ 要在 Django 中设置权限,可以使用 Django 自带的权限系统或者其他第三方权限库,比如 django-guardian。下面我会分别介绍这两种方式。

使用 Django 自带的权限系统

  1. 定义权限: 首先,在 User 模型中,你已经定义了 user_type 字段,表示用户类型。你可以根据这个字段来定义不同的权限。
  2. 授权: 在视图中,你可以使用 Django 提供的装饰器 @permission_classes 或者在视图方法中动态设置 (这种不推荐)permission_classes 属性来限制不同用户类型的权限。

示例代码:

from rest_framework.permissions import IsAuthenticated, BasePermission

class UserPermissions(BasePermission):
    def has_permission(self, request, view):
        # 如果是超级管理员,则允许访问
        if request.user.user_type == 3:
            return True
        # 如果是普通管理员或注册用户,则拒绝访问
        return False

class UsersViews(ViewSet):
    @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated, UserPermissions])
    def admin_dashboard(self, request):
        # 普通管理员和超级管理员才能访问该接口
        # 具体逻辑...

使用第三方权限库 django-guardian

  1. 安装 django-guardian: 首先,你需要安装 django-guardian。

    pip install django-guardian
    
  2. 设置权限: 使用 django-guardian,你可以在模型级别定义权限,并且授予特定用户或用户组这些权限。

示例代码:

from guardian.shortcuts import assign_perm

# 给用户或用户组授权
assign_perm('custom_permission_code', user, obj)

# 检查权限
user.has_perm('custom_permission_code', obj)

​ 在这两种方式中,你都可以根据用户类型来限制其权限。使用 Django 自带的权限系统更简单,但功能较为有限;而使用 django-guardian 则更加灵活,可以实现更复杂的权限控制。

【 2 】权限类的编写

  1. 写个类,继承BasePermission
  2. 重写has_permission 方法
  3. 在方法内部进行权限校验
    • ​ 有权限返回True
    • ​ 没有权限返回False
  4. 定制返回提示:self.message

这个方法要注意的是权限跟我们表中定义的要一致

1.写个类,继承BasePermission

from rest_framework.permissions import BasePermission
# 只有超级用户才能访问,其它用户都没有权限
class UserPermission(BasePermission):
    # 2.重写has_permission 方法
    def has_permission(self, request, view):
        # 判断权限,如果有权限,返回True,如果没有权限返回False
        # 获取当前登录用户---》request.user中拿到--》通过了认证
        print(request.user)
        # tian
        
        # 3.在方法内部进行权限校验
        if request.user.user_type == 3:
            return True
        else:
            return False
  • views.py

  • 注意写权限的前提是有在登陆接口以及完成认证的前提下才能实现权限控制。

from rest_framework.generics import GenericAPIView
from rest_framework.viewsets import ViewSet
from .permissions import UserPermission
from .authaction import LoginAuth

# ViewSet = GenericAPIView + APIView
class Userview(ViewSet):
    # 视图中配置权限
    authentication_classes = [LoginAuth]
    permission_classes = [UserPermission]

    def list(self, request):

        return Response('123--list')

image-20240417201841578

  • urls.py

from two.views import BookView, PublishView, Userview ,UsersViews # ,BookDetailView
# 自动生成路由
from rest_framework.routers import SimpleRouter, DefaultRouter
from django.urls import path,include
# 2 实例化得到对象
# router = SimpleRouter()
router = DefaultRouter()
# 3 执行对象的方法
# 这个方法就是自动帮我们生成映射  {'get': 'list', 'post': 'create'}

router.register('user', Userview, 'user')

router.register('users', UsersViews, 'users')

router.register('books', BookView, 'books')


# 4 对象.属性 拿到值
# print(router.urls)
# [<URLPattern '^books/$' [name='books-list']>, <URLPattern '^books/(?P<pk>[^/.]+)/$' [name='books-detail']>]
urlpatterns = [
    # path('request/', ReqsuetView.as_view()),
]
# 5 把自动生成的路由,放到 urlpatterns中
urlpatterns += router.urls

image-20240417201346359

进一步的完善

  • permissions.py

from rest_framework.permissions import BasePermission
from rest_framework.permissions import AllowAny
from django.contrib.auth.models import AnonymousUser
# 只有超级用户才能访问,其它用户都没有权限
class UserPermission(BasePermission):
    def has_permission(self, request, view):
        # 判断权限,如果有权限,返回True,如果没有权限返回False
        # 获取当前登录用户---》request.user中拿到--》通过了认证
        print(type(request.user))
        if request.user.is_authenticated :
            if request.user.user_type == 3:
                return True
            else:
                # 只要使用了choice,拿到数字对应的中文
                # get_字段名_display()
                # 验证你是否为超级管理员
                user_type=request.user.get_user_type_display()
                self.message=f'您是:{user_type},您没有权限访问'
                return False
        else:
            self.message = '您还没登录呢'
            return False

【 3 】权限的使用

  1. 写一个类,继承BasePermission
  2. 重写 has_permission方法
  3. 在方法中,判断用户的权限
    • ​ -如果有权限,返回True
    • ​ -如果没有权限,返回False
    • ​ -错误提示:self.message=‘错误信息’
  4. 使用
    • ​ -局部视图类使用
    • ​ -项目全局使用
    • ​ -局部禁用
  5. 权限控制可能更复杂
    • ​ -访问控制列表:acl 用户和权限一对多
    • ​ -rbac权限控制
  6. 用户类型字段–》choice–》choice对应的中文–》用户对象.get_字段名_display()
    • ​ request.user–>经过了认证类
  • 全局


REST_FRAMEWORK = {
        'DEFAULT_PERMISSION_CLASSES': [
        'two.permissions.UserPermission'
        # 应用程序.py文件名.权限类名
    ],
}
  • 局部

# ViewSet = GenericAPIView + APIView
class Userview(ViewSet):
    # 认证
    authentication_classes = [LoginAuth]
    # 视图中配置权限
    permission_classes = [UserPermission]

    def list(self, request):
        return Response('123--list')

image-20240417210133096

# 局部禁用---》登录接口
class Userview(ViewSet):
	permission_classes = []

image-20240417202947942

【 4 】基本模板

from rest_framework.permissions import BasePermission

class CommonPermission(BasePermission):
    message = '没有权限进行此操作'

    def has_permission(self, request, view):
        # 在这里实现你的权限认证逻辑
        # 假设你有一个条件判断来检查用户是否有权限执行操作
        if request.user.has_perm('some_permission'):
            return True
        else:
            # 定制返回的中文消息
            self.message = '您没有执行此操作的权限'
            return False

在你的视图中,你可以按照你的需求进行设置:

  1. 局部使用:将权限类添加到视图的 permission_classes 属性中。
from rest_framework.views import APIView
from .permissions import CommonPermission

class BookDetailView(APIView):
    permission_classes = [CommonPermission]
    # 其他视图逻辑...
  1. 全局使用:在全局设置中配置默认权限类。
# settings.py

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'app01.permissions.CommonPermission',
    ],
}
  1. 局部禁用:如果你想在某个视图中禁用权限验证,可以将 permission_classes 设置为空列表。
from rest_framework.views import APIView

class BookDetailView(APIView):
    permission_classes = []
    # 其他视图逻辑...

【 四 】频率控制(重要)

【 0 】前言

rest_framework.throttling 模块中的 BaseThrottleSimpleRateThrottle 类用于实现 API 的请求速率限制,以防止恶意用户或者过度使用 API 的情况发生。

  • BaseThrottle:这是一个基础的节流器类,它定义了节流器的基本结构和方法。你可以通过继承 BaseThrottle 类来创建自定义的节流器,实现你想要的节流逻辑。
  • SimpleRateThrottle:这是一个简单的速率节流器类,它允许你设置简单的速率限制规则,例如在一段时间内允许的最大请求数。你可以通过子类化 SimpleRateThrottle 并设置适当的速率限制来限制用户在给定时间段内的请求频率。

​ 使用这两个类,你可以在 Django REST Framework 的 API 中实现请求速率限制,以防止用户过度使用你的 API 资源,保护服务器免受恶意攻击或者过度消耗的情况。

【 1 】 频率类

频率类控制某个接口访问的频率(次数)。 频率类常用于反爬,因为爬虫的请求次数太快,导致服务器压力很大, 服务器崩溃。

通过一个需求,来学习频率类: 查询所有的接口,同一个ip一分钟只能访问5次。 (可以通过IP地址控制、也可以用户控制访问频率)

频率类还是熟悉的套路。 这里继承simpleRateThrottle:

from rest_framework.throttling import BaseThrottle,SimpleRateThrottle

class WecelomeThrottling(SimpleRateThrottle):

    rate = '8/m'  # N/m  一分钟8次

    def get_cache_key(self, request, view):
        # 返回什么,就会以什么做限制--》ip地址限制;
        return request.META.get('REMOTE_ADDR')
        
        # # 用户id做限制
        # return request.user.pk

【 2 】重写get_cache_key()

image-20240417205327398

【 3 】频率的使用

  1. 写一个类,继承SimpleRateThrottle
  2. 重写get_cache_key(),返回唯一值,返回什么就以什么作为限制条件
    • ​ -ip,设备唯一id号,用户id
  3. 类属性:rate=‘3/d’
  4. 使用
    • -局部视图类使用
    • -项目全局使用
    • -局部禁用
from rest_framework.generics import GenericAPIView
from rest_framework.viewsets import ViewSet
from .permissions import UserPermission
from .authaction import LoginAuth
from .throttling import WecelomeThrottling

# ViewSet = GenericAPIView + APIView
class Userview(ViewSet):
    # 认证
    authentication_classes = [LoginAuth]
    # 视图中配置权限
    # permission_classes = []
    permission_classes = [UserPermission]
    # 频率的视图类的配置
    throttle_classes = [WecelomeThrottling]

    def list(self, request):

        return Response('123--list')

image-20240417210237463

# 局部使用
from .throttling import WecelomeThrottling
class Userview(ViewSet):
    # 频率的视图类的配置
    throttle_classes = [WecelomeThrottling]
# 全局使用
REST_FRAMEWORK = {

    'DEFAULT_THROTTLE_CLASSES': ['two.throttling.WecelomeThrottling'],
}

有可能会出现这种错误


CRITICALS:
two.Usersuper: (auth.C010) <class 'two.models.Usersuper'>.is_authenticated must be an attribute or property rather than a method. Ignoring this is a security issue as anonymous users will be treated as authenticated!

解决方法

在models.py中添加

class Usersuper(AbstractUser):
                AUTH_USER_MODEL = 'app01.UserInfo'  ---'应用名.表名'


    phone = models.CharField(max_length=32)

    user_type = models.IntegerField(choices=((1, '注册用户'), (2, '普通管理员'), (3, '超级管理员')), default=1)

    @property
    def is_authenticated(self):
        return True

【 4 】源码解析


class BaseThrottle:
    """
    Rate throttling of requests.
    """

    def allow_request(self, request, view):
        """
        Return `True` if the request should be allowed, `False` otherwise.
        """
        raise NotImplementedError('.allow_request() must be overridden')

    def get_ident(self, request):
        """
        Identify the machine making the request by parsing HTTP_X_FORWARDED_FOR
        if present and number of proxies is > 0. If not use all of
        HTTP_X_FORWARDED_FOR if it is available, if not use REMOTE_ADDR.
        """
        xff = request.META.get('HTTP_X_FORWARDED_FOR')
        remote_addr = request.META.get('REMOTE_ADDR')
        num_proxies = api_settings.NUM_PROXIES

        if num_proxies is not None:
            if num_proxies == 0 or xff is None:
                return remote_addr
            addrs = xff.split(',')
            client_addr = addrs[-min(num_proxies, len(addrs))]
            return client_addr.strip()

        return ''.join(xff.split()) if xff else remote_addr

    def wait(self):
        """
        Optionally, return a recommended number of seconds to wait before
        the next request.
        """
        return None

​ 这段源代码定义了一个基础的请求限速器(Throttle)类 BaseThrottle,用于限制请求的频率。让我来解释其中的几个关键方法:

  1. allow_request(self, request, view):
    • 这是一个用于判断是否允许请求的方法。当请求到达时,框架会调用这个方法来确定是否应该处理该请求。如果返回 True,则表示允许请求继续处理;如果返回 False,则表示不允许请求继续处理。
    • 默认情况下,这个方法会抛出 NotImplementedError 异常,要求子类必须覆盖这个方法,并提供自己的实现逻辑。
  2. get_ident(self, request):
    • 这个方法用于识别发出请求的客户端的身份。
    • 它首先尝试从请求的 HTTP_X_FORWARDED_FOR 头部中获取客户端的真实 IP 地址,如果存在多个代理,则会选择最后一个代理的 IP 地址。
    • 如果没有设置 NUM_PROXIES(代理数量)或者代理数量为 0,或者没有 HTTP_X_FORWARDED_FOR 头部,它会直接返回 REMOTE_ADDR(客户端的 IP 地址)。
    • 如果以上条件都不满足,它会返回空字符串。
  3. wait(self):
    • 这是一个可选方法,用于返回建议的等待时间,以便下一个请求。
    • 默认情况下,它会返回 None,表示不需要等待,可以立即处理下一个请求。

​ 这些方法提供了请求限速器的基本功能,但具体的限速逻辑需要在子类中实现。你可以创建一个自定义的 Throttle 类,继承 BaseThrottle 并覆盖 allow_request 方法来定义你自己的请求限速策略。

创建一个BaseThrottle.py

from datetime import datetime, timedelta
from django.core.cache import cache
from rest_framework.throttling import BaseThrottle
from rest_framework.settings import api_settings


class IPBasedRateThrottle(BaseThrottle):

    def allow_request(self, request, view):

        # 获取客户端 IP 地址
        ip_address = self.get_ident(request)

        # 获取请求限制的配置
        throttle_rates = getattr(view, 'throttle_rates', api_settings.DEFAULT_THROTTLE_RATES)

        # 检查是否存在IP对应的限制配置
        if ip_address in throttle_rates:
            # 获取IP对应的限制配置
            rate, num_seconds = throttle_rates[ip_address]

            # 构造缓存键名
            cache_key = f'ratelimit_{ip_address}'

            # 从缓存中获取上次请求的时间
            last_request_time = cache.get(cache_key)

            # 如果不存在上次请求的时间,或者距离上次请求已经超过限制的时间间隔,则允许请求
            if last_request_time is None or datetime.now() - last_request_time > timedelta(seconds=num_seconds):
                # 更新缓存中的请求时间
                cache.set(cache_key, datetime.now(), timeout=num_seconds)
                return True

        # 如果没有设置限制或者未达到限制条件,则允许请求
        return True
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, DestroyModelMixin, ListModelMixin, \
    UpdateModelMixin
from rest_framework.viewsets import GenericViewSet
from .models import Book2

from rest_framework.generics import GenericAPIView
from rest_framework.viewsets import ViewSet
from .permissions import UserPermission
from .authaction import LoginAuth
from .throttling import WecelomeThrottling
# 排序 过滤
from rest_framework.filters import OrderingFilter,SearchFilter
from rest_framework.decorators import permission_classes
# 分页
from .page import CommonCursorPagination,CommonPageNumberPagination,CommonLimitOffsetPagination
# 自定义的频率类
from .BaseThrottle import IPBasedRateThrottle
# 自己定义的过滤类
from two.filters import PriceRangeFilter
class BOOKViewss(GenericViewSet, ListModelMixin,CreateModelMixin):
    # 配置认证类为LoginAuth
    authentication_classes = [LoginAuth]
    throttle_classes = [IPBasedRateThrottle]
    permission_classes = []

    queryset = Book2.objects.all()
    serializer_class = BookSerializer
    # 排序 、 过滤 、 过滤类
    filter_backends = [OrderingFilter,SearchFilter,PriceRangeFilter]
    # pagination_class = CommonPageNumberPagination # 分页方式只能选择一种
    # pagination_class = CommonLimitOffsetPagination # 分页方式只能选择一种
    # pagination_class = CommonCursorPagination  # 分页方式只能选择一种

    ordering_fields = ['price','name']

    # 也可以多个字段模糊匹配
    search_fields = ['name', 'publish']

【 5 】使用步骤总结

    -第一步:写一个类,继承SimpleRateThrottle,重写get_cache_key
    -第二步:get_cache_key返回什么就以什么做限制,必须写类属性 scope='字符串'
    -第三步:配置文件中配置
      'DEFAULT_THROTTLE_RATES': {
        '字符串': '3/m',  # key:ip_1m_3 对应频率类的scope属性, value: 3/m 一分钟访问3次
          # m:分钟
          # s:秒
          # h:小时
          # d:天
        },

     -第四步:局部使用和全局使用
from rest_framework.throttling import BaseThrottle,SimpleRateThrottle
class MyThrottling(SimpleRateThrottle):
    scope = 'ip_1m_3'  # 必须写scope是一个字符串

    def get_cache_key(self, request, view):
        # 返回什么,就以什么做限制(ip地址)
        # 客户端ip地址
        return request.META.get('REMOTE_ADDR')

        # 以用户id限制
        # return request.user.id

  • 49
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值