DRF---三种分页方式,jwt介绍和原理,django快速使用jwt,自定义用户表签发和认证token

三种分页方式


1) PageNumberPagination---基本分页

前端访问网址形式:

GET  http://127.0.0.1:8000/students/?page=4

可以在子类中定义的属性:

  • page_size 每页数目
  • page_query_param 前端发送的页数关键字名,默认为”page”
  • page_size_query_param 前端发送的每页数目关键字名,默认为None
  • max_page_size 前端最多能设置的每页数量
from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination,CursorPagination

class BookPagination(PageNumberPagination):
    page_size = 2  # 默认每页展示的条数
    page_query_param = 'page'  # 查询页码的参数
    page_size_query_param = 'size'  # 控制每页显示的条数 ?size=3
    max_page_size = 5  # 控制每页最多显示多少条数

2) LimitOffsetPagination---偏移分页

前端访问网址形式:

GET http://127.0.0.1/four/students/?limit=100&offset=400

可以在子类中定义的属性:

  • default_limit 默认限制,默认值与PAGE_SIZE设置一直
  • limit_query_param limit参数名,默认’limit’
  • offset_query_param offset参数名,默认’offset’
  • max_limit 最大limit限制,默认None
class CommonLimitOffsetPagination(LimitOffsetPagination):
    default_limit = 2    # 每页显示多少条
    limit_query_param = 'limit'   # 取多少条
    offset_query_param = 'offset' #从第0个位置偏移多少开始取数据
    max_limit = 5     # 最大限制条数

3) CursorPagination---游标分页

前端访问网址形式:

GET http://127.0.0.1/four/students/?cursor=cD0xNQ%3D%3D

可以在子类中定义的属性:

  • cursor_query_param:默认查询字段,不需要修改
  • page_size:每页数目
  • ordering:按什么排序,需要指定
class CommonCursorPagination(CursorPagination):
    cursor_query_param = 'cursor'  # 查询的名字  等同于  page=xx
    page_size = 3  # 每页显示多少条
    ordering = 'id'  # 排序规则,必须是表中有的字段,一般用id

4) 继承APIView实现分页

from rest_framework.views import APIView
from rest_framework.viewsets import ViewSet
from rest_framework.response import Response
class BookView(ViewSet):
    def list(self,request):
        books=Book.objects.all()
        # 分页
        # paginator=PageNumberPagination()
        paginator=CommonLimitOffsetPagination()  # 实例化自己写的分页类
        #分页过后的数据
        qs=paginator.paginate_queryset(books,request,self)
        #序列化
        ser=BookSerializer(qs,many=True)
        # 第一种方式:每页总条数,上一页,下一页
        # return Response(ser.data)
        # 第二种:自己凑
        # return Response({
        #     'count':books.count(),
        #     'next': paginator.get_next_link(),
        #     'previous':paginator.get_previous_link(),
        #     'results': ser.data
        # })
        # 第三种;直接使用分页类的方法
        return paginator.get_paginated_response(ser.data)

jwt介绍和原理


1) 介绍

jwt:Json web token

彻底理解cookie,session,token:彻底理解cookie,session,token - 墨颜丶 - 博客园 (cnblogs.com)

cookie是存放在客户端浏览器的键值对

sessionId:asdfasdf----》放到cookie中

session是存放在服务端的键值对

asdfasdf:{id:3,name:lqz}
dddddd:{id:4,name:pyy}

token: 字符串分三段

-第一段:头(header)

jwt的头部承载两部分信息:

  • 声明类型,这里是jwt
  • 声明加密的算法 通常直接使用 HMAC SHA256

完整的头部就像下面这样的JSON:

{
  'typ': 'JWT',
  'alg': 'HS256'
}

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.

-第二段:载荷(payload)

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明

一般可以用来存放用户信息:

{
    "id":3,
    "name":"lqz",
    "age":17,
}

然后将其进行base64加密,得到JWT的第二部分。

-第三段:签名(signature)

JWT的第三部分是一个签证信息,这个签证信息第一段和第二段通过某种加密方式+秘钥加密得到一个字符串。

将这三部分用.连接成一个完整的字符串,构成了最终的jwt。

2) jwt原理

jwt认证算法:签发与校验

"""
1)jwt分三段式:头.体.签名 (head.payload.sgin)
2)头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性的
3)头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法
4)头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
{
	"company": "公司信息",
	...
}
5)体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
{
	"user_id": 1,
	...
}
6)签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
{
	"head": "头的加密字符串",
	"payload": "体的加密字符串",
	"secret_key": "安全码"
}
'''

签发:根据登录请求提交来的 账号 + 密码 + 设备信息 签发 token

"""
1)用基本信息存储json字典,采用base64算法加密得到 头字符串
2)用关键信息存储json字典,采用base64算法加密得到 体字符串
3)用头、体加密字符串再加安全码信息存储json字典,采用hash md5算法加密得到 签名字符串

账号密码就能根据User表得到user对象,形成的三段字符串用 . 拼接成token返回给前台
"""

校验:根据客户端带token的请求 反解出 user 对象

"""
1)将token按 . 拆分为三段字符串,第一段 头加密字符串 一般不需要做任何处理
2)第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户,过期时间和设备信息都是安全信息,确保token没过期,且时同一设备来的
3)再用 第一段 + 第二段 + 服务器安全码 不可逆md5加密,与第三段 签名字符串 进行碰撞校验,通过后才能代表第二段校验得到的user对象就是合法的登录用户
"""

3) drf项目的jwt认证开发流程(重点)

"""
1)用账号密码访问登录接口,登录接口逻辑中调用 签发token 算法,得到token,返回给客户端,客户端自己存到cookies中

2)校验token的算法应该写在认证类中(在认证类中调用),全局配置给认证组件,所有视图类请求,都会进行认证校验,所以请求带了token,就会反解出user对象,在视图类中用request.user就能访问登录的用户

注:登录接口需要做 认证 + 权限 两个局部禁用
"""

4) base64的编码和解码

base64的长度一定是4的倍数,如果不到用=补齐

 import json
    import base64
    d={'typ': 'JWT','alg': 'HS256'}
    s=json.dumps(d)
    print(s)

    #把字符串进行b64编码
    res=base64.b64encode(bytes(s,encoding='utf-8'))
    print(res)


    #把b64编码的字符串,解码
    b=b'eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJIUzI1NiJ9'
    res=base64.b64decode(b)
    print(res)

django中快速使用jwt


1) 官网

http://getblimp.github.io/django-rest-framework-jwt/

2) 安装

pip3 install djangorestframework-jwt

3) 使用:

# 1 创建超级用户
python3 manage.py createsuperuser
# 2 配置路由urls.py
from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
    path('login/', obtain_jwt_token),
]
# 3 postman测试
向后端接口发送post请求,携带用户名密码(username,password),即可看到生成的token

4) 修改返回格式

要求登录成功返回格式:

{
    code:100,msg:"登录成功",
    token:asdfasdf,
    username:lqz
}

写一个函数,在配置文件配置

def jwt_response_payload_handler(token, user=None, request=None):
    return {'code': 100, 'msg': '登陆成功', 'token': token, 'username': user.username}

配置文件配置

JWT_AUTH = {
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.utils.jwt_response_payload_handler',
}

jwt的验证


只需要在视图类中加入一个认证类,一个权限类

class BookView(ViewSet):
    authentication_classes = [JSONWebTokenAuthentication,]
    permission_classes = [IsAuthenticated,]

需要在请求头中携带token

-请求头中key:Authorization
-请求头的value值:jwt token串

jPuX8A.png

自定义用户表签发token


自己定义用户表,实现签发token功能,自己写登录接口。

from rest_framework_jwt.settings import api_settings

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
from rest_framework.decorators import action
class UserView(ViewSet):
    @action(methods=['POST'],detail=False)
    def login(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        user = UserInfo.objects.filter(username=username, password=password).first()
        if user:
            # 登录成功---》签发token
            payload = jwt_payload_handler(user)  # 根据当前登录用户获取荷载
            token = jwt_encode_handler(payload)  # 根据荷载生成token

            return Response({'code': 100, 'msg': '登录成功', 'token': token})
        else:
            return Response({'code': 101, 'msg': '用户名或密码错误'})

自定义认证类验证token


从JSONWebTokenAuthentication的父类BaseJSONWebTokenAuthentication里的authenticate方法里找源码,编写认证类。

import jwt
from .models import UserInfo
from rest_framework_jwt.settings import api_settings
from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
jwt_get_username_from_payload = api_settings.JWT_PAYLOAD_GET_USERNAME_HANDLER
class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        token=request.META.get('HTTP_TOKEN')
        try:
            payload = jwt_decode_handler(token)
        except jwt.ExpiredSignature:
            msg = 'token过期了'
            raise exceptions.AuthenticationFailed(msg)
        except jwt.DecodeError:
            msg = 'token解码错误'
            raise exceptions.AuthenticationFailed(msg)
        except jwt.InvalidTokenError:
            msg = '解析token未知错误'
            raise exceptions.AuthenticationFailed(msg)
        # try:
        #     payload = jwt_decode_handler(token)
        # except Exception:
        #     raise exceptions.AuthenticationFailed('token错误')
        user = UserInfo.objects.filter(pk=payload['user_id']).first()
        return user, token

posted @ 2022-06-23 20:41  早安_1207  阅读(132)  评论(0)    收藏  举报
返回顶端