def 接口文档的书写,,jwt介绍,,base64的编码和解码,,jwt的开发重点及快速使用,,关于双token认证问题,,人脸识别,,多方式登录__编写流程

接口文档

  

后端把接口写好后
    -登录接口   /api/v1/login  --->post---> name pwd--->编码格式
    -注册接口
    -查询所有图书带过滤接口
# 前端人员需要根据接口文档,进行前端开发

# 前后端需要做对接----》对接第一个东西就是这个接口文档---》前端照着接口文档开发

# 公司3个人,每个人开发了10个接口,3个人都要同时写接口文档

# 接口文档的编写形式
    -1 world,md,编写,大家都可以操作,写完放在git,公司的文档管理平台上
    -2 第三方的接口文档平台(收费)
        https://www.showdoc.com.cn/
    -3 公司自己开发接口文档平台:就跟第三方很像
    -4 公司使用开源的接口文档平台,搭建
        -YAPI:百度开源的
        -https://zhuanlan.zhihu.com/p/366025001
        
    -5 项目自动生成接口文档--drf
        -coreapi
        -swagger
        
        
# 使用coreapi自动生成接口文档
    -使用步骤:
        -1 安装:pip3 install coreapi
        -2 加一个路由
            from rest_framework.documentation import include_docs_urls    
            urlpatterns = [
                path('docs/', include_docs_urls(title='站点页面标题'))
            ]
        -3 在视图类上加注释
        class BookView(GenericViewSet, ListModelMixin):
            '''
            返回所有图书接口
            '''
            queryset = Book.objects.all()
            serializer_class = BookSerializer
            # throttle_classes = [CommonThrottling]
            # throttle_classes = [MyThrottling]

        class PublishView(ListModelMixin, RetrieveModelMixin, CreateModelMixin, GenericViewSet):
            """
            list:
            返回出版社列表数据
            retrieve:
            返回出版社详情数据
            create:
            新增出版社
            """
            queryset = Book.objects.all()
            serializer_class = BookSerializer
            
        -3 序列化类中写描述
        class BookSerializer(serializers.ModelSerializer):
        class Meta:
            model = Book
            fields = ['id', 'name', 'price', 'publish', 'authors', 'publish_detail', 'author_list']
            extra_kwargs = {
                'name': {'help_text':"出版社名字",'required':False},
                'publish': {'write_only': True,'help_text':"出版社id号"},
                'authors': {'write_only': True},
                'publish_detail': {'read_only': True},
                'author_list': {'read_only': True},
            }
        -4 配置文件中配置:
        'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
        -5 表模型或序列化类的字段上写 help_text--->会显示在接口文档的字段介绍上
        
        -6 访问地址:
        http://127.0.0.1:8000/docs/

jwt介绍

  

 jwt:Json web token (JWT) ,常被用于认证,它是一个前端登录认证的方案
    -session和cookie方案
# cookie,session,token发展史
    https://www.cnblogs.com/liuqingzheng/articles/17858187.html
    
    
# jwt是token的一种,jwt本质就是token

# token
   1 不在服务端存储   (session在服务端存储)
   2 token 有三段,需要有个加密方式和秘钥,来签发token[生成签名],和验证token[验证签名]

# jwt构成---》三段式--》使用 . 分割 ---》使用base64编码
# 典型的jwt-token串
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
# 第一段:头部:header   
    -eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    -声明加密算法,公司信息。。。
    
# 第二段:荷载:payload  
     -eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
    -用户id,用户信息,token过期时间exp,token签发时间iat。。。


# 第三段:签名 signature 
    -TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
    -通过某种加密方式+秘钥,把头和荷载加密后得到
    -使用它,做到防篡改,防伪造

base64的编码和解码

  base64 编码解码方式,不是加密方式

  

import json
import base64
# user_info={'user_id':999,'username':'lqz'}
# # 转成字符串,使用base64编码
# user_info_str=json.dumps(user_info)
# print(user_info_str)
#
# # 使用base64编码--->bytes格式
# res=base64.b64encode(user_info_str.encode('utf-8'))
# print(res)  #  eyJ1c2VyX2lkIjogOTk5LCAidXNlcm5hbWUiOiAibHF6In0=


###base64解码
# res=base64.b64decode('eyJ1c2VyX2lkIjogOTk5LCAidXNlcm5hbWUiOiAibHF6In0=')
res=base64.b64decode('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9')
print(res)



# base64实际用途
    1 用在jwt中
    2 前后端交互,可能使用base64编码后交互
        -百度登录:密码先加密---》使用base64编码---》向后端发送
    3 图片使用base64 前后端传递
        -12306就是
        
        
# base64特点
    -大小写数字组合,有时候,结尾会带  =
    -base64编码后的字符串,一定是4个倍数,如果不足,用 = 补齐
    -base64解码可能会出错,不足4的倍数为,我们自己使用=补齐,所以,最多补3个=

jwt的开发重点

  前后端登录认证的方式

  

1 登录,签发token---》登录接口
    1 用户携带用户名,密码到后端
    2 校验用户名密码是否正确,如果正确
    3 签发token,按照jwt逻辑生成三段,返回给前端
 2 认证token---》认证类
    1 用户访问我们需要登录的接口
    2 携带token过来--》请求头,请求地址。。
    3 后端验证用户携带的token,是否被篡改,是否是伪造的,如果没问题
    4 认证通过,继续后续的逻辑

jwt快速使用

drf中借助于第三方:
    -djangorestframework-jwt  老,年久失修
    -djangorestframework-simplejwt 都在用

# 下载:
    pip3 install djangorestframework-simplejwt
# 写登录--》人家帮咱们写了--》用户表-->用的auth的user表

使用步骤

  

# 1 安装
pip install djangorestframework-simplejwt

# 2 路由层
from rest_framework_simplejwt.views import token_obtain_pair, token_verify, token_refresh

urlpatterns = [
    path('login/', token_obtain_pair),
    path('verify/', token_verify),
    path('refresh/', token_refresh),
]

# 3 配置文件
import datetime
SIMPLE_JWT = {
    # token有效时长
    'ACCESS_TOKEN_LIFETIME': datetime.timedelta(minutes=30),
    # token刷新后的有效时间
    'REFRESH_TOKEN_LIFETIME': datetime.timedelta(days=1),
}

# 4 注册app
INSTALLED_APPS = [
    ...
    'rest_framework_simplejwt',
    ...
]

# 5 迁移表,创建超级用户
    createsuperuser
    
    
    
# 6 测试登录接口,验证接口,刷新接口



# 7 双token认证:
    access,真正使用的token
    refresh:用来更新access
    access过期时间很短,过期后,需要重新生成access的token保证token的安全

注: 只要没过期,之前签发的access[token]和后来刷新签发的token都能使用

关于双token认证问题

  

单token
    -用户登录---》签发token--》有过期时间
        3 minute----》重新登录
        7 天------》7天都不需要登录--->被别人截货到---》不安全
        
        
# 双token
    -用户登录---》签发了两个token---》目前的verify验证接口,只要是它签发的token,都会认证通过
        access:过期时间短 3分
        refresh:过期时间长7天
        
    -用户正常用:都用access,不用refresh
    -access过会就过期了,一旦过期,就永不了---》通过refresh这个token,调用刷新接口,再签发一个access
    -通过refresh再次签发token这个过程,是不需要登录的,对用户就无感知
    
    -后续再用access这个token发送请求
   
    -好处是:access一旦被别人截取到---》拿着模拟发送请求,只能在有效时间内,很快就会失效

# 认证类:不能使用refresh的token

人脸识别

人脸识别---》登录---》登录后还要签发token 

-登录:
  1 用户名密码
  2 手机号验证码
  3 一键登录
  4 扫码登录
  5 人脸登录

登录成功了,付款,付大额,弹出人脸识别 

--------做二次认证

认证

认证类----->局部配置和全局配置

使用步骤:

  

1 局部配置,必须配合权限类
    class BookView(APIView):
        # 局部加:认证类--->带来认证信息,会校验,不带认证信息,不管,需要配合一个权限类使用
        authentication_classes = [JWTAuthentication]
        permission_classes = [IsAuthenticated]  # 权限类,没登录的用户没权限
        
    2 前端访问:格式必须如下,放在请求头中
        Authorization :Bearer access的token
        
        
# 全局使用---它写的登录,去除了认证
    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': [
            'rest_framework_simplejwt.authentication.JWTAuthentication',
        ],
        'DEFAULT_PERMISSION_CLASSES': [
            'rest_framework.permissions.IsAuthenticated',
        ],
    }
    
--------- 验证使用 refresh的token能不能认证通过

定制返回格式

# 1 我们的目标,定制返回格式
{
    code:100,
    msg:'登录成功',
    username:'lqz',
    access:asdfasdf.asdfasdf.asdfasdf
    refresh:asdfas.ere.we
}

# 2 顺便看了一下荷载内容
    -access和refresh是有区分的
    
    
# 3 步骤
     1 写个序列化类,重写validate ,返回什么,前端看到什么
    from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
    from rest_framework_simplejwt.views import TokenObtainPairView
    class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
        # 往荷载中加东西
        @classmethod
        def get_token(cls, user):  # user就是登录成功,查到的用户
            token = super().get_token(user)  # 签发token
            token['name'] = user.username  # 往荷载中加用户名
            return token

        def validate(self, attrs):
            old_data = super().validate(attrs)
            data = {'code': 100,
                    'msg': '登录成功成功',
                    'username': self.user.username,
                    'refresh': old_data['refresh'],
                    'access': old_data['access']
                    }
            return data
      2 写类方法:get_token--》返回的token中就是荷载的内容
        
      3 配置文件配置
        SIMPLE_JWT = {
            "TOKEN_OBTAIN_SERIALIZER": "app01.serializer.MyTokenObtainPairSerializer",
        }

多方式登录

用户输入: 用户名/或手机号/或邮箱 +密码 都能登录 签发 token

  ------username:手机号/邮箱/名 ,+password

使用auth的user表

 

如果是老项目,已经迁移过数据,按照如下操作

1   删库
2   删除项目中app的迁移文件
3   删除源码中 admin和auth中得迁移记录
4   扩写auth的user表
5   重新迁移

 

多方式登录接口,编写流程:

一条版:

  

class UserView(APIView):
    authentication_classes = ()
    permission_classes = ()

    def post(self, request):
        # 1  request取出用户名和密码
        username = request.data.get('username')
        password = request.data.get('password')
        # 2  使用正则判断用用户名是邮箱,手机号还是用户名,分别去查询当前用户
        if re.match(r'^1[3-9][0-9]{9}$', username):
            user = User.objects.filter(mobile=username).first()
        elif re.match(r'^.+@.+$', username):
            user = User.objects.filter(email=username).first()
        else:
            user = User.objects.filter(username=username).first()
        if user and user.check_password(password):
            # 3 校验密码
            # 4 签发token
            refresh = TokenObtainPairSerializer.get_token(user)
            # 5 返回给前端
            return Response({'code': 100, 'msg': '成功', 'access': str(refresh.access_token), 'refresh': str(refresh)})
        else:
            return Response({'code': 101, 'msg': '用户名或密码错误'})

-------写法2:

路由:

urlpatterns = [
    path('login_mul/', views.UserView.as_view()),
]

视图类:

  

from rest_framework.generics import GenericAPIView
from .serializer import LoginSerializer


class UserView(GenericAPIView):
    authentication_classes = ()
    permission_classes = ()
    serializer_class = LoginSerializer
    def post(self, request):
        ser = self.get_serializer(data=request.data)
        if ser.is_valid():  # 会执行字段自己的校验(没有),执行局部钩子(没有),执行全局钩子(写了:校验用户,签发token)
            # context 是视图类和序列化列之间沟通的桥梁
            access = ser.context.get('access')
            refresh = ser.context.get('refresh')
            username = ser.context.get('username')
            return Response({'code': 100, 'msg': '成功', 'username': username, 'access': access, 'refresh': refresh})
        else:
            return Response({'code': 101, 'msg': '用户名或密码错误11'})

序列化类

from rest_framework import serializers
from .models import User
import re
from rest_framework.exceptions import ValidationError

class LoginSerializer(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()

    # 写全局钩子
    def validate(self, attrs):
        # 校验用户,签发token
        username = attrs.get('username')
        password = attrs.get('password')
        # 2  使用正则判断用用户名是邮箱,手机号还是用户名,分别去查询当前用户
        if re.match(r'^1[3-9][0-9]{9}$', username):
            user = User.objects.filter(mobile=username).first()
        elif re.match(r'^.+@.+$', username):
            user = User.objects.filter(email=username).first()
        else:
            user = User.objects.filter(username=username).first()
        if user and user.check_password(password):
            # 3 校验密码
            # 4 签发token
            refresh = TokenObtainPairSerializer.get_token(user)
            self.context['access'] = str(refresh.access_token)
            self.context['refresh'] = str(refresh)
            self.context['username'] = user.username
            return attrs
        else:
            raise ValidationError('用户名或密码错误')

-----------写法3:

序列化类:

  

class LoginSerializer(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()

    # 写全局钩子
    def validate(self, attrs):
        # 校验用户,签发token
        username = attrs.get('username')
        password = attrs.get('password')
        # 2  使用正则判断用用户名是邮箱,手机号还是用户名,分别去查询当前用户
        if re.match(r'^1[3-9][0-9]{9}$', username):
            user = User.objects.filter(mobile=username).first()
        elif re.match(r'^.+@.+$', username):
            user = User.objects.filter(email=username).first()
        else:
            user = User.objects.filter(username=username).first()
        if user and user.check_password(password):
            # 3 校验密码
            # 4 签发token
            refresh = TokenObtainPairSerializer.get_token(user)
            data = {'code': 100,
                    'msg': '登录成功成功',
                    'username': self.user.username,
                    'refresh':str(refresh),
                    'access': str(refresh.access_token)
                    }
            return data
        else:
            raise ValidationError('用户名或密码错误')

视图类:

  

class UserView(GenericAPIView):
    authentication_classes = ()
    permission_classes = ()
    serializer_class = LoginSerializer

    def post(self, request):
        ser = LoginSerializer(data=request.data)
        if ser.is_valid():  # 会执行字段自己的校验(没有),执行局部钩子(没有),执行全局钩子(写了:校验用户,签发token)
            # ser.validated_data # 字典,校验过后的数据
            return Response(ser.validated_data )
        else:
            return Response({'code': 101, 'msg': '用户名或密码错误11'}

 

posted @ 2024-01-03 19:46  拆尼斯、帕丁顿  阅读(49)  评论(0)    收藏  举报