# 后端把接口写好后:
?? ?登录接口:/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自动生成接口文档:
????* 安装:? pip??install coreapi? ??? ? ? ? ? ?总路由: from django.contrib import admin from rest_framework.documentation import include_docs_urls from django.urls import path,include urlpatterns = [ path('admin/', admin.site.urls), path('docs/', include_docs_urls(title='站点页面标题')), path('app/',include('app01.urls')), ] app01路由: from rest_framework.routers import SimpleRouter from . import views router=SimpleRouter() router.register('books', views.BookView, 'books') router.register('pubilsh',views.PublishView,'pulish') urlpatterns = [ ] urlpatterns += router.urls
视图类加注释:? 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
序列化类: 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}, ? ? ? ? ? ? }
配置文件: INSTALLED_APPS = [ ... 'app01.apps.App01Config', 'rest_framework', 'rest_framework_simplejwt', ] REST_FRAMEWORK = { 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema', }
???????*表模型或序列化类的字段上写 help_text--->会显示在接口文档的字段介绍上
? ? ? ?*访问地址:?http://127.0.0.1:8000/docs/
# 概念:JWT全名Json web token ,常被用于认证,它是一个前端登录认证的方案,是token? ? 的一种,jwt本质就是token
# cookie,session,token发展史:????????????????????????????????????????????????? ? ? ? ? ??????????????????? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.cnblogs.com/liuqingzheng/articles/17858187.html# token:
? ? ? ? ? ?*不在服务端存储(session在服务端存储)
? ? ? ? ? ?*token 有三段,需要有个加密方式和秘钥,来签发token,和验证token
# jwt-token串,构成:三段式,使用 . 分割,base64编码eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ # 第一段:头部:header ?? ?? ?-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 ?? ?-声明加密算法,公司信息... ? ?? # 第二段:荷载:payload ? ??? -eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9 ? ? -用户id,用户信息,token过期时间exp,token签发时间iat... # 第三段:签名 signature? ?? ?-TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ ? ? -通过某种加密方式+秘钥,把头和荷载加密后得到 ? ? -使用它,做到防篡改,防伪造
# ?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('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9') print(res)
# base64特点:
? ?????????大小写数字组合,有时候,结尾会带 ?=
? ? ????????base64编码后的字符串,一定是4个倍数,如果不足,用 = 补齐,最多补3个=# base64实际用途:
?? ?1、用在jwt中
? ? 2、前后端交互,可能使用base64编码后交互
? ? ? ? ????????百度登录:密码先加密,使用base64编码,向后端发送
? ? 3、图片使用base64 前后端传递? ? 12306就是
# JWT是前后端登录认证的方式:
登录?--签发token(登录接口)
?? ?????????????????1 用户携带用户名,密码到后端
?? ?????????????????2 校验用户名密码是否正确,如果正确
? ? ????????????????3 签发token,按照jwt逻辑生成三段,返回给前端
认证token---认证类
?? ?????????????????1 用户访问我们需要登录的接口
? ? ????????????????2 携带token过来--请求头,请求地址...
? ? ? ? ? ? ? ? ? ??3 后端验证用户携带的token,是否被篡改,是否是伪造的,如果没问题
? ????????????????? 4 认证通过,继续后续的逻辑# jwt快速使用:
1、安装, drf中借助于第三方:
????????????????pip install djangorestframework-simplejwt2、路由层:
from rest_framework.routers import SimpleRouter from rest_framework_simplejwt.views import token_obtain_pair, token_verify, token_refresh from . import views from django.urls import path router=SimpleRouter() router.register('books', views.BookView, 'books') router.register('pubilsh',views.PublishView,'pulish') urlpatterns = [ path('login/', token_obtain_pair), # 登录,签发token-r-》只有这个用的多 path('verify/', token_verify), # 验证token 是否有效 path('refresh/', token_refresh), # token有过期时间,过期不能使用,在过期之前,可以刷新 ]
3、配置文件:
INSTALLED_APPS = [ ... 'app01.apps.App01Config', 'rest_framework', 'rest_framework_simplejwt', ] import datetime SIMPLE_JWT = { ? ? # token有效时长 ? ? 'ACCESS_TOKEN_LIFETIME': datetime.timedelta(minutes=30), ? ? # token刷新后的有效时间 ? ? 'REFRESH_TOKEN_LIFETIME': datetime.timedelta(days=1), }
4、迁移表,创建超级用户---?createsuperuser
5、测试登录接口,验证接口,刷新接口# 双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
# 人脸识别---登录---登录后还要签发token
??登录:
? ? ?? ?1 用户名密码
? ? ? ? 2 手机号验证码
? ? ? ? 3 一键登录
? ? ? ? 4 扫码登录
? ? ? ? 5 人脸登录
# 登录成功了,付款,付大额,弹出人脸识别,做二次认证
# 认证类:局部配置、全局配置
局部配置:必须配合权限类
class BookView(APIView): ? ?# 局部加:认证类--->带来认证信息,会校验,不带认证信息,不管,需要配合一个权限类使用 ? ?authentication_classes = [JWTAuthentication] # 认证类 ? ?permission_classes = [IsAuthenticated] ?# 权限类,没登录的用户没权限
# 前端访问:格式必须如下,放在请求头中
? ? ?? ?????????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是有区分的? ?# 第二段:荷载:payload ? eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9 用户id,用户信息,token过期时间exp,token签发时间iat...
# 3 步骤
写个序列化类: # 重写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
配置文件配置 SIMPLE_JWT = { ? ? "TOKEN_OBTAIN_SERIALIZER": "app01.serializer.MyTokenObtainPairSerializer", }
视图层: # 定制返回格式(单纯做定制) from rest_framework.response import Response from rest_framework.views import APIView class BookView(APIView): def get(self, request): return Response("好多书啊")
路由层: from rest_framework_simplejwt.views import token_obtain_pair, token_verify, token_refresh from django.urls import path urlpatterns = [ path('login/', token_obtain_pair), # 登录,签发token-r-》只有这个用的多 path('verify/', token_verify), # 验证token 是否有效 path('refresh/', token_refresh), # token有过期时间,过期不能使用,在过期之前,可以刷新 ]
# 用户输入:用户名或手机号或邮箱 +密码 都能登录,签发token
????????username:手机号/邮箱/名????????password:xxx
# 使用auth的user表
# 新建项目没问题,如果是老项目,迁移过数据了,按照如下操作:
?? ?????????1 删库
? ? ????????2 删除项目中app的迁移文件
? ? ????????3 删除源码中 admin和auth中得迁移记录
? ? ????????4 扩写auth的user表
? ????????? 5 重新迁移#?面条版登录(简单版)
moedels.py from django.db import models from django.contrib.auth.models import AbstractUser class User(AbstractUser): mobile = models.CharField(max_length=24) icon = models.ImageField(upload_to='icon', default='icon/default.png')
# 迁移表格: python manage.py makemigrations python manage.pymigrate # 创建超级用户: python manage.py createsuperuser
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': '用户名或密码错误'})
urls.py path('books/', views.UserView.as_view())
views.py from rest_framework.response import Response 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'})
serializers.py from rest_framework import serializers from .models import User import re from rest_framework.exceptions import ValidationError from rest_framework_simplejwt.serializers import TokenObtainPairSerializer 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('用户名或密码错误')
# 直接在序列化类里输出格式
views.py 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'})
serializers.py 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('用户名或密码错误')