认证类

认证类的作用:
实现某些接口,登录之后能访问,有的接口不登录就能访问

前期准备

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

所以这个token可以放在请求的什么位置?
请求首行 or 请求头 or 请求体

img

  • 对于post请求 ---> token可以放请求体
  • 对于get 请求 ---> get请求没有请求体,token可以放在请求首行的地址栏
  • 其实, 一般token放在请求头上传到后端。

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

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

综上所述,在这个我们的示例下token暂时通过get请求的地址栏(请求首行)传到后端。

通过实现一个需求,来学习认证组件:

# 查询所有图书,不需要登录就能访问
# 查询单个图书,需要登录才能访问

由于两个接口,一个登录才能访问,一个不登录也能访问,所以最好把两个接口分开两个类写。

image-20230207092052051

通过simpleRouter生成路由,并实现对方法的映射。

image-20230207092007155

自定义认证类

我们可以在视图类中添加authentications_classes类属性来实现对整个视图类的认证校验。

只要是继承了GenericAPIView的视图类中都有如下属性:
authentications_classes = [认证类,]
这个认证类需要我们自己写。

在app目录下,新键一个authenticate.py文件来存放我们自己写的认证类。

自己写的认证类,需要继承drf的认证基类BaseAuthentication

image-20230207092409078

这个基类,有个authenticate方法必须得重写否则会抛出异常:

image-20230207092430701

通过query_params方法获取get请求问号后面携带的token参数。根据请求中是否携带token,判断是否登录。

image-20230207093128978

需要注意的是,当登录成功,要求固定返回两个值:

  • return (请求对应的用户,token)

image-20230207093219090

当前用户没有登录则抛出AuthenticationFailed异常。
这个异常从如下位置导入:
from rest_framework.exceptions import AuthenticationFailed

至此我们已经把自定义认证类写好了,接下来需要在我们的视图进行配置。

配置认证类

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

image-20230207093243130

请注意,可以给一个视图添加多个认证类,他会从左到右执行。

这个视图类所有的方法,都需要经过认证类认证,才能执行。
image-20230207093528433

我们通过认证类实现了上述需求。

全局配置

在django settings中配置可以实现全局生效(所有CBV)。

image-20230207222232589

注意:
不要在django配置文件中乱导入不使用的东西,否则会报错:
【Django drf】认证类 权限类 频率类 过滤类 排序类 分页类-小白菜博客
这是因为先加载配置文件,再加载django项目。而app01是在django项目里面的,还没有成功加载,就导入,所以会报错。

局部禁用

【Django drf】认证类 权限类 频率类 过滤类 排序类 分页类-小白菜博客
也就是在authentication_classes类属性写一个空列表, 这表示该CBV局部禁用认证。

认证组件使用步骤

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

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

认证类编写难点:

  • token在请求的哪里携带?
  • 如何获取用户上传的token?
  • 如何认证用户登录了?

基于类中方法的认证(了解)

认证类是通过类属性配置的,所以一个认证类会对于整个视图类生效。但有时候我们会有如下的需求:
一个CBV类中,某些方法,进行认证,其他方法,不走认证。

要实现这个需求有如下方式:

  • 方式一: 把我们写好的认证类添加到action装饰器的authentication_classes参数中.image-20230208201342795

  • 方式二: 使用原生django提供的装饰器
    如果想给视图类中的方法添加装饰器, 并且这个装饰器是我们手写的函数装饰器时,这个装饰器不能直接给类中的方法添加.
    而是需要使用django提供的一个装饰器来添加.
    没错,通过装饰器来添加装饰器!

    导入from django.utils.decorators import method_decorator

    image-20230208202535100

但是正常来说,应该把需要认证的接口和不需要认证的接口拆分开。(登录和未登录)

代码:

### 视图
# 查询所有
class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer


# 查询单个
class BookDetailView(ViewSetMixin, RetrieveAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    # authentication_classes = [LoginAuth]  # 需要写一个认证类,需要咱们写


### 认证类代码

class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        # 在这里实现认证,如果是登录的,继续往后走返回两个值,如果不是抛异常
        # 请求中是否携带token,判断是否登录,放在地址栏中
        token = request.query_params.get('token', None)
        if token:  # 前端传入token了,去表中查,如果能查到,登录了,返回两个值[固定的:当前登录用户,token]
            user_token = UserToken.objects.filter(token=token).first()
            if user_token:
                return user_token.user, token
            else:
                # 没有登录抛异常
                raise AuthenticationFailed('token认证失败')
        else:
            raise AuthenticationFailed('token没传')

            
            
### 路由代码
router.register('books', views.BookView, 'books')
router.register('books', views.BookDetailView, 'books')

权限类

是否有这种情况,你登录小视频网站,但是有些视频即使登录了,也无法观看,只有VIP才能观看。
这就是因为你没有权限,从上述例子也可以看出,权限是认证的后一层校验。

对于程序员来说,需要给接口设置权限:

# 即便登录成功了,有些接口,还是不能访问,因为没有权限
# 登录后,有的接口有权限访问,有的没有权限访问

通过一个需求,来学习权限类:

# 查询单个和查询所有接口,都要登录才能访问----》全局认证
	-查询单个需要超级管理员才能访问
    -查询所有,所有登录用户都能访问

前期准备

权限是一个字段,需要在user表中加入user_type字段。
【Django drf】认证类 权限类 频率类 过滤类 排序类 分页类-小白菜博客
通过user_type字段表示不同身份的用户。
备注: 如果模型字段使用了choice参数,就可以使用get_user_type_display方法拿到字段对应的中文。

重写has_permission()

和认证类套路相似,写一个权限类:
这个权限类继承权限基类,需要重写基类的has_permission()方法,在这个方法中实现权限认证。

需要注意的是这个方法的返回值:

  • 如果有权限,return True
  • 如果没有权限,return True

查看源码:

image-20230207101824499

首先需要获取当前的用户,才能去数据库里查询该用户的user_type字段:image-20230207101939401

通过request.user可以拿到当前的用户,这个request.user属性是经过认证之后添加到request对象中的.
也就是认证函数authenticate的第一个返回值:
image-20230207102033381

认证方法的返回值第一个是当前用户,会放在request.user。在权限类中可以使用request.user将其拿出来。

判断权限:
image-20230207102149486

通过类属性配置权限:
【Django drf】认证类 权限类 频率类 过滤类 排序类 分页类-小白菜博客
注意,权限和认证都可以配置多个。

添加权限不足信息

控制权限给前端的提示信息:
image-20230207102524740

权限相关说明:

  • 登录接口需要禁用权限,查询所有接口也需要禁用权限。
  • 权限并不是这么简单,权限类的逻辑可以很复杂。用表保存用户的权限,当请求来了去权限表获取单个用户的权限.

权限组件使用步骤

# 1 写一个权限类,继承BasePermission
# 2 重写has_permission方法,在该方法在中实现权限认证,在这方法中,request.user就是当前登录用户
# 3 如果有权限,返回True
# 4 没有权限,返回False,定制返回的中文: self.message='中文'
# 5 局部使用和全局使用
	-局部:只在某个视图类中使用【当前视图类管理的所有接口】
        class BookDetailView(ViewSetMixin, RetrieveAPIView):
    		permission_classes = [CommonPermission]
    -全局:全局所有接口都生效
          REST_FRAMEWORK = {
            'DEFAULT_PERMISSION_CLASSES': [
                'app01.permissions.CommonPermission',
            ],

        }
    
    -局部禁用:
    	 class BookDetailView(ViewSetMixin, RetrieveAPIView):
            permission_classes = [] 

频率类

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

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

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

重写get_cache_key()

需要重写get_cache_key()方法:
image-20230207103827811

两种频率限制方法

这个方法返回什么,就以什么做频率限制:
(可以返回ip 或 用户ID)
image-20230207104225347

如何获取客户端的ip地址?
使用reuquest.META,
在request.META是一个字典, 这里面存放着请求头的各种数据。

image-20230207104057623

  • 以ip地址做限制:
    image-20230207104458276

  • 以用户id做限制:
    image-20230207104318851

  • 以电脑做限制,需要配合前端,将电脑的唯一标识传入后端。

配置固定的类属性scope

如果需要在视图类中配置频率需要做两件事:

  1. 频率类设置一个固定的类属性scope:
    image-20230207104524501

  2. 还需要配置django settings:
    【Django drf】认证类 权限类 频率类 过滤类 排序类 分页类-小白菜博客
    可以发现类属性的值,和这里的键有对应关系。

    • 5/m:表示一分钟访问五次
    • 5/h:表示一小时访问五次

    他是会把字符串取第一个字符来判断是小时还是分钟。
    所以也可以这样写:5/maya ---> 一分钟五次

局部使用在类中设置频率类:
image-20230207104749844

服务重启会重新计算这个时间。

全局配置频率类:

跟上面不同, 需要进行两项配置:image-20230208211519250

局部禁用频率类:

image-20230207105334752

频率组件使用步骤

# 1 写一个频率类,继承SimpleRateThrottle
# 2 重写get_cache_key方法,返回什么,就以什么做限制----》ip,用户id做限制
# 3 配置一个类属性:scope = 'book_5_m'
# 4 在配置文件中配置
  'DEFAULT_THROTTLE_RATES': {
        'book_5_m': '5/m',
    },
# 5 局部使用和全局使用
	-局部:只在某个视图类中使用【当前视图类管理的所有接口】
        class BookDetailView(ViewSetMixin, RetrieveAPIView):
    		throttle_classes = [CommonThrottle]
    -全局:全局所有接口都生效
          REST_FRAMEWORK = {
             'DEFAULT_THROTTLE_CLASSES': ['app01.throttling.CommonThrottle'],

        }
    
    -局部禁用:
    	 class BookDetailView(ViewSetMixin, RetrieveAPIView):
            throttle_classes = [] 

过滤类

restful规范中,要求了请求地址中带过滤条件。

  • 5个接口中,只有一个接口需要有过滤和排序,也就是查询所有接口.
  • 频率\认证\权限校验是多个接口都需要的.

需求:

查询 所有图书接口,查询以  红  开头的所有图书

内置过滤类 SearchFilter

class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    #  SearchFilter内置的,固定用法,模糊匹配
    #  就有过滤功能了,指定按哪个字段过滤
    filter_backends = [SearchFilter]
    # search_fields = ['name']  # 可以按名字模糊匹配
    search_fields = ['name','price']  # 可以按名字模糊匹配或价格模糊匹配
    
 # 可以使用的搜索方式
	http://127.0.0.1:8000/api/v1/books/?search=红  # name或price中只要有红就会搜出来
            
            
# 继承APIView如何写,完全自己写,麻烦,但是清晰     

配置过滤类的方式:
必须继承GenericAPIView,如果是APIView需要自己写过滤相关逻辑.

在视图类中配置:image-20230207112512865

需要指定按照哪些字段进行过滤。
image-20230207112533960

通过search_field类属性,指定按照哪些字段过滤:
image-20230207112548134

这个search_fields是固定的写法,同时前端必须用search提交查询参数。

image-20230207112710108

可以按照多个字段,按照名字或价格字段模糊匹配:

image-20230207113202266

示例:
image-20230207112834279

只要名字或价格带939的, 都可以搜出来。

继承APIView怎么实现过滤?

【Django drf】认证类 权限类 频率类 过滤类 排序类 分页类-小白菜博客全部都要自己写。
但是我们可以使用GenericAPIView自定义过滤类。

第三方过滤类 django-filter

# 安装:django-filter
class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = []
    authentication_classes = []
    throttle_classes = []
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['name','price']  # 支持完整匹配  name=聊斋11&price=933
    
    
 # 支持的查询方式
http://127.0.0.1:8000/api/v1/books/?price=939
http://127.0.0.1:8000/api/v1/books/?price=939&name=红楼猛

安装:

pip3.8 install django-filter -i https://pypi.tuna.tsinghua.edu.cn/simpleimage-20230207113707019

使用注意:
image-20230207114104792

使用了第三方过滤类,就不使用search_fields类属性,而是使用filterset_fields类属性:

image-20230207114205745

前端示例:image-20230207114237506

支持精确匹配。模糊匹配就是只要含有就可以匹配。

自定义过滤类

# 查询价格大于100的所有图书
	http://127.0.0.1:8000/api/v1/books/?price_gt=100
                   
 #第一步; 定义一个过滤类,继承BaseFilterBackend,重写filter_queryset方法
class CommonFilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        # 在里面实现过滤,返回qs对象,就是过滤后的数据
        price_gt = request.query_params.get('price_gt', None)
        if price_gt:
            qs = queryset.filter(price__gt=int(price_gt))
            return qs

        else:
            return queryset
        
 # 第二步:配置在视图类上
class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    filter_backends = [CommonFilter]  # 可以定制多个,从左往右,依次执行

需求:

# 查询价格大于100的所有图书
	http://127.0.0.1:8000/api/v1/books/?price_gt=100

定义一个过滤类,继承BaseFilterBackend,重写filter_queryset方法
image-20230207114506944

编写过滤类:
image-20230207114652855

queryset参数存放所有模型对象。

配置多个过滤类,会依次从左往右执行:
image-20230207114932712

如果想在自定义过滤类中实现类属性设置过滤字段,可以通过反射获取类中的属性:
image-20230207114830465

view就是我们的视图类,可以从视图类中获取类属性。

踩坑:
模型类存放的是charfield charfield不支持__gt
image-20230207115405724

image-20230207115428356

排序类

使用drf内置的排序类.

# 内置的就够了
class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [OrderingFilter]  
    ordering_fields = ['price']
    
 # 支持的查询方法:
    http://127.0.0.1:8000/api/v1/books/?ordering=price
    http://127.0.0.1:8000/api/v1/books/?ordering=-price
   http://127.0.0.1:8000/api/v1/books/?ordering=-id,price

先将排序类配置到filter_backends
image-20230207115727588

再设置排序字段:price
image-20230207115934285

按照价格排序ordering=price
如下是按照价格升序.
image-20230207115837164

按照价格逆序ordering=-price

image-20230207115858885

支持多个排序:比如先按照价格排序,再按照购买量排序。

只需要在视图类配置排序字段:
ordering_field = ['price','sales']

前端格式如下:
http://127.0.0.1:8000/api/v1/books/?ordering=-price,sales按照逗号隔开.(先按照价格逆序,在按照销售额升序)

分页类

#  分页,只有查询所有接口,才有分页

# drf内置了三个分页器,对应三种分页方式

#内置的分页类不能直接使用,需要继承,定制一些参数后才能使用



# 分页使用,自定义一个分页类(三种)
class CommonPageNumberPagination(PageNumberPagination):
    page_size = 2  # 每页显示2条
    page_query_param = 'page'  # page=10  查询第10页的数据,每页显示2条
    page_size_query_param = 'size'  # page=10&size=5    查询第10页,每页显示5条
    max_page_size = 5  # 每页最大显示10条


# LimitOffset
class CommonLimitOffsetPagination(LimitOffsetPagination):
    default_limit = 3  # 每页显示2条
    limit_query_param = 'limit'  # limit=3   取3条
    offset_query_param = 'offset'  # offset=1  从第一个位置开始,取limit条
    max_limit = 5
    # offset=3&limit=2      0  1 2 3 4 5


# app 用下面

class CommonCursorPagination(CursorPagination):
    cursor_query_param = 'cursor'  # 查询参数
    page_size = 2  # 每页多少条
    ordering = 'id'  # 排序字段
# 配置在视图类上即可
class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = []
    authentication_classes = []
    throttle_classes = []
    # 之前的东西一样用 ,内置的分页类不能直接使用,需要继承,定制一些参数后才能使用
    # pagination_class = PageNumberPagination
    #基本分页方式(基本是这种,网页端):http://127.0.0.1:8000/api/v1/books/?page=2&size=3

    # pagination_class = LimitOffsetPagination
    # 偏移分页 http://127.0.0.1:8000/api/v1/books/?limit=4&offset=1
    # 从第一条开始,取4条

    pagination_class = CommonCursorPagination
    # 游标分页,只能下一页,上一页,不能跳到中间,但它的效率最高,大数据量分页,使用这种较好

只有查询所有接口,才需要分页。

drf内置三个分页器,对应三种分页方式。
image-20230207121029314

数据10000条给前端,前端卡死了都,所以前端直接拿分好的数据。

类属性添加分页类:
image-20230207120913904

分页属性只能配置一种分页方式。

内置的分页类不能直接用,而需要继承之后,自己定制一些参数,之后才能使用。

PageNumberPagination(网页)

image-20230207121152987

源码:
image-20230207121211607

需要设置四个类属性:
【Django drf】认证类 权限类 频率类 过滤类 排序类 分页类-小白菜博客
page_size可以写在配置文件中。

四个属性:

image-20230207121810282可以通过前端的size定制每页显示的条数。size的条数会受到最大条数的限制。

配置分页器:

image-20230207121728653

响应中包含前页和后页:

image-20230207121527495

第一页显示三条:

image-20230207121602862

LimitOffsetPagination

也是配置类属性:
image-20230207121917518

会将所有的数据排序:相当于做切片。

image-20230207121946513

第一页:
image-20230207122057371

从第一条数据开始拿,往后面拿4条数据:image-20230207122130196

CursorPagination(大数据,app)

游标分页源码:
image-20230207122235344

具体使用:类属性重复会先用哪个?
image-20230207122317296

游标分页,只能下一页,上一页,不能跳到中间,他的效率最高。适合大数据量的时候。

前面的分页, 是将数据从头开始检索,数据量越大,越慢,效率越低.

image-20230207122515846

在分页时维护一个游标。
image-20230207122640034

返回地址带链接:image-20230207122728714

游标分页默认的配置:

image-20230207122748135

游标分页默认按照时间排序,如果没有在分页类中定义ordering属性就会报错。
这是因为ordering必须是表的字段,比如我们的表里肯定有id字段,所以可以设置ordering为'id':
image-20230207122856031