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: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 -通过某种加密方式+秘钥,把头和荷载加密后得到 -使用它,做到防篡改,防伪造
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个=
1 登录,签发token---》登录接口 1 用户携带用户名,密码到后端 2 校验用户名密码是否正确,如果正确 3 签发token,按照jwt逻辑生成三段,返回给前端 2 认证token---》认证类 1 用户访问我们需要登录的接口 2 携带token过来--》请求头,请求地址。。 3 后端验证用户携带的token,是否被篡改,是否是伪造的,如果没问题 4 认证通过,继续后续的逻辑
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--》有过期时间 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
-登录:
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", }
使用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'}