• drf——分页、jwt介绍与原理、jwt快速使用、jwt源码分析、jwt自定义返回格式、自定义用户签发token、自定义token认证类


    系列文章目录

    drf
    第一章 django web开发模式、api接口、api接口测试工具、restful规范、序列化反序列化、drf安装使用

    第二章 drf的使用、APIView源码分析、Request源码分析、Serializer的序列化

    第三章 Serializer的反序列化、字段与参数、局部与全局钩子、ModelSerializer使用

    第四章 drf认证、权限、频率源码分析、全局异常处理、自动生成接口文档、RBAC介绍



    一、分页

    分页是针对于查询所有的接口使用的,针对于分页需要在GenericAPIView的基础上设置对应的pagination_class来启动分页功能

    分页在drf中有三种:

    PageNumberPagination
    LimitOffsetPagination
    CursorPagination

    1.PageNumberPagination

    基本分页:正常的查第几页,每页显示多少条的方式,有前一页和下一页的url(常用)

    使用时需要创建一个类继承PageNumberPagination然后设置对应的类属性

    pagtions.py

    class CommPageNumberPagination(PageNumberPagination):
    
        page_size = 2  # 每页显示的内容数
        page_query_param = 'page' # 当前在第几页的参数如:?page=5
        page_size_query_param = 'size' # 分页显示的内容数如: ?size=3 不能超过最大内容显示数
        max_page_size = 5 # 最大内容显示数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    views.py

    class BookView(GenericViewSet,ListModelMixin):
        queryset = models.Book.objects.all()
        serializer_class = serializer.BookSerializer
        pagination_class = pagtions.CommPageNumberPagination #设置对应的分页类
    
    • 1
    • 2
    • 3
    • 4

    分页返回的响应为:

    "count": 6,  第几页数据
        "next": "http://127.0.0.1:8000/api/v1/book/?page=2", 下一页的url
        "previous": null, 上一页的url
        "results": [
            { 此处为该页对应显示的数据条数
                "id": 1, 
                "name": "asd1",
                "price": "12.56"
            },
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.LimitOffsetPagination

    偏移分页是根据当前页面来计算第一条坐标然后根据偏移量来获取当前页的数据

    pagtions.py

    class commLimitOffsetPagination(LimitOffsetPagination):
        default_limit = 2  # 每页显示多少条
        limit_query_param = 'limit'  # 取多少条
        offset_query_param = 'offset'  # 从第0个位置偏移多少开始取数据
        max_limit = 5 # 一页最多显示数据数
    
    • 1
    • 2
    • 3
    • 4
    • 5

    views.py

    class BookView1(GenericViewSet,ListModelMixin):
        queryset = models.Book.objects.all()
        serializer_class = serializer.BookSerializer
        pagination_class = pagtions.commLimitOffsetPagination
    
    • 1
    • 2
    • 3
    • 4

    分页返回的响应为:

    "count": 6,  第几页数据
        "next": "http://127.0.0.1:8000/api/v1/book/?page=2", 下一页的url
        "previous": null, 上一页的url
        "results": [
            { 此处为该页对应显示的数据条数
                "id": 1, 
                "name": "asd1",
                "price": "12.56"
            },
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.CursorPagination

    游标分页 不能精准跳转到某一页,只能在前一页或者下一页 但是因为是游标所以查询速度很快

    pagtions.py

    class commCursorPagination(CursorPagination):
        cursor_query_param = 'cursor'  # 查询的名字  等同于  page=xx
        page_size = 2  # 每页显示多少条
        ordering = 'id' # 排序规则,必须是表中有的字段,一般用id
    
    • 1
    • 2
    • 3
    • 4

    views.py

    class BookView2(GenericViewSet,ListModelMixin):
        queryset = models.Book.objects.all()
        serializer_class = serializer.BookSerializer
        pagination_class = pagtions.commCursorPagination
    
    • 1
    • 2
    • 3
    • 4

    返回的响应结果

    {
        "next": "http://127.0.0.1:8000/api/v1/book2/?cursor=cD0y",  下一页的url
        "previous": null, 上一页的url
        "results": [ 返回的当前页的数据
            {
                "id": 1,
                "name": "asd1",
                "price": "12.56"
            },
            {
                "id": 2,
                "name": "asd2",
                "price": "13.56"
            }
        ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    4.继承APIView实现分页

    相对于GenericAPIView而言APIView需要我们自己使用分页类来进行分页数据的组装

    第一种方式实例化自建分页类后调用分页类的方法拼凑

    class BookView3(ViewSet):
    
        def list(self, request):
            books = models.Book.objects.all()
            paginator= pagtions.CommPageNumberPagination()
            qs = paginator.paginate_queryset(books, request, self) # 分页后的数据
            res = serializer.BookSerializer(qs, many=True) # 序列化数据
            # 第一种返回方式
            return Response({
            'code':200, 
            'msg':'查询全部书籍成功',  
            'count':books.count(), 
            'data':res.data, 
            'next': paginator.get_next_link(), 
            'previous':paginator.get_previous_link()
            })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    第二种方式实例化自建分页类后使用分页类的方法获取最后的结果

    class BookView3(ViewSet):
    
        def list(self, request):
            books = models.Book.objects.all()
            paginator= pagtions.CommPageNumberPagination()
            qs = paginator.paginate_queryset(books, request, self) # 分页后的数据
            res = serializer.BookSerializer(qs, many=True) # 序列化数据
    		return paginator.get_paginated_response(res.data)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    二、jwt介绍与原理

    cookie是存放在客户端浏览器的键值对,主要目的是为了完成信息交互,为用户展示个性化的页面
    使用cookie,容易被伪造,因为cookie是存放在客户端的之后就开发了session
    session是存放在服务端的键值对,但是对于服务器的内存占用过大
    所以出现了token,token是存放在客户端的一个加密字符串,用户访问服务器时携带上token来展示用户自己的身份,方便服务器发送对应数据

    jwt主要作用是进行交互式通信的,可以动态的为用户展示个性化的页面,jwt集成了token的生成、加密解密、认证
    token:
    字符串:分三段
    第一段:为头,软件所属公司,加密方式等
    第二段:为荷载,放用户信息如{id:3,name:lqz}
    第三段:为签名,把第一段和第二段通过某种加密方式+秘钥加密得到一个字符串

    token典型样子:每个点最为字符串各阶段的分割
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

    jwt的认证原理拿到第一段和第二段使用同样的加密方式+秘钥再加密,得到字符串跟第三段比较,如果一样,表示没有被篡改,如果不一样,表明被篡改了,token不能用了,如果没有被篡改,取出第二段 当前登录用户的信息,这个时候传递给认证类来进行认证,认证完成之后在进行别的正常数据交互处理

    jwt组成
    第一段 header为{‘typ’: ‘JWT’,‘alg’: ‘HS256’}
    第二段 payload为荷载是真正用户的数据
    {
    “sub”: “1234567890”, # 过期时间
    “id”:3
    “name”: “John Doe”,
    “admin”: true
    }
    第三段 signature为签名
    header (base64编码后的)
    payload (base64编码后的)
    secret


    三、jwt快速使用

    1.首先是安装jwt

    pip3 install djangorestframework-jwt
    
    • 1

    2.使用的表是django的auth的user表创建一个用户

    3.配置路由
    urls.py

    from rest_framework_jwt.views import obtain_jwt_token
    urlpatterns = [
        path('login/', obtain_jwt_token),
    ]
    
    • 1
    • 2
    • 3
    • 4

    4.向login发送post请求携带上对应的用户名密码这是会返回一个token

    在这里插入图片描述


    四、jwt源码分析

    根据from rest_framework_jwt.views import obtain_jwt_token寻找到ObtainJSONWebToken,该类用于返回一个基于APIView的已登陆用户的token

    class ObtainJSONWebToken(JSONWebTokenAPIView):
    	此处为jwt自身的序列器
        serializer_class = JSONWebTokenSerializer
    
    • 1
    • 2
    • 3
    class JSONWebTokenSerializer(Serializer):
        def __init__(self, *args, **kwargs):
            super(JSONWebTokenSerializer, self).__init__(*args, **kwargs)
    
            self.fields[self.username_field] = serializers.CharField()
            self.fields['password'] = PasswordField(write_only=True)
    
    	该方法返回了项目中正在活跃的用户(已登录)
        @property
        def username_field(self):
            return get_username_field()
    
    	全局钩子函数
        def validate(self, attrs):
        	credentials 中获取到了post请求携带的用户名以及密码
            credentials = {
                self.username_field: attrs.get(self.username_field),
                'password': attrs.get('password')
            }
    		此处获取credentials中所有的vlaue调用jwt的authenticate方法来获取user
            if all(credentials.values()):
                user = authenticate(**credentials)
    
                if user:
                    if not user.is_active:
                        msg = _('User account is disabled.')
                        raise serializers.ValidationError(msg)
    
                    payload = jwt_payload_handler(user)
    
                    return {
                        'token': jwt_encode_handler(payload),
                        'user': user
                    }
                else:
                    msg = _('Unable to log in with provided credentials.')
                    raise serializers.ValidationError(msg)
            else:
                msg = _('Must include "{username_field}" and "password".')
                msg = msg.format(username_field=self.username_field)
                raise serializers.ValidationError(msg)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    VerifyJSONWebToken是用于检验token是否有效的

    class VerifyJSONWebToken(JSONWebTokenAPIView):
        serializer_class = VerifyJSONWebTokenSerializer
    
    • 1
    • 2
    class VerifyJSONWebTokenSerializer(VerificationBaseSerializer):
        def validate(self, attrs):
            token = attrs['token']
    
            payload = self._check_payload(token=token) 根据token获取有效荷载(也就是用户的数据部分)
            user = self._check_user(payload=payload) 根据有效数据部分获取对应的用户
    
            return {
                'token': token,
                'user': user
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    RefreshJSONWebToken将基于视图来判断登录用户是否还在活跃,如果还在活跃则会刷新已到期的token返回新的token给用户

    class RefreshJSONWebToken(JSONWebTokenAPIView):
        serializer_class = RefreshJSONWebTokenSerializer
    
    • 1
    • 2
    class RefreshJSONWebTokenSerializer(VerificationBaseSerializer):
        def validate(self, attrs):
            token = attrs['token']
    
            payload = self._check_payload(token=token)
            user = self._check_user(payload=payload)
            # Get and check 'orig_iat'
            orig_iat = payload.get('orig_iat')
    
            if orig_iat:
                # Verify expiration
                refresh_limit = api_settings.JWT_REFRESH_EXPIRATION_DELTA
    
                if isinstance(refresh_limit, timedelta):
                    refresh_limit = (refresh_limit.days * 24 * 3600 +
                                     refresh_limit.seconds)
    
                expiration_timestamp = orig_iat + int(refresh_limit)
                now_timestamp = timegm(datetime.utcnow().utctimetuple())
    
                if now_timestamp > expiration_timestamp:
                    msg = _('Refresh has expired.')
                    raise serializers.ValidationError(msg)
            else:
                msg = _('orig_iat field is required.')
                raise serializers.ValidationError(msg)
    
            new_payload = jwt_payload_handler(user)
            new_payload['orig_iat'] = orig_iat
    
            return {
                'token': jwt_encode_handler(new_payload),
                'user': user
            }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    五、jwt自定义返回格式

    jwt的返回格式不一定符合我们的要求,这个时候我们需要通过修改配置文件告诉jwt我们需要的返回格式

    首先创建一个函数

    def jwt_response_payload_handler(token, user=None, request=None):
        return {'code': 100, 'msg': '登陆成功', 'token': token, 'username': user.username}
    
    
    • 1
    • 2
    • 3
    JWT_AUTH = {
    'JWT_RESPONSE_PAYLOAD_HANDLER':'app01.jwt_response.Jwt_response',
    }
    
    • 1
    • 2
    • 3

    六、jwt自定义签发token

    from django.contrib.auth.models import auth
    from rest_framework_jwt.settings import api_settings
    jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
    jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
    class CustomUser(ViewSet):
        def login(self, request):
            dicts = {'code': 200, 'msg':'登陆成功'}
            user = auth.authenticate(request, username=request.data.get('username'), password = request.data.get('password'))
            if user:
                payload = jwt_payload_handler(user)
                token = jwt_encode_handler(payload)
                dicts['token'] = token
            else:
                dicts['code'] = 1001
                dicts['msg'] = '用户名或密码错误'
            return Response(dicts)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    七、jwt自定义token认证类

    只需要继承BaseAuthentication然后重写authenticate方法
    在需要认证的类中配置authentication_classes即可

    from rest_framework.authentication import BaseAuthentication
    from rest_framework import exceptions
    from rest_framework_jwt.settings import api_settings
    from app01.models import User
    jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
    class CommAuthentication(BaseAuthentication):
        def authenticate(self, request):
            jwt_value = request.META.get('token')
            try:
                payload = jwt_decode_handler(jwt_value)
            except Exception:
                raise exceptions.AuthenticationFailed('token错误')
                user = User.objects.filter(pk=jwt_value['user_id']).first()
            return user, jwt_value
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
  • 相关阅读:
    Maven 01
    使用Jmeter进行http接口性能测试
    UE4 给模型设置碰撞避坑
    Linux常见概念及命令介绍
    NoSQL 数据库之redis键值设计、批处理优化、服务端优化、集群配置优化
    C++学习笔记(三十一)
    干货!半监督预训练对话模型 SPACE
    引蜘蛛秒收录软件
    Vuex(十)
    自动生成电子印章
  • 原文地址:https://blog.csdn.net/kdq18486588014/article/details/125430765