1 jwt自定义表签发
1.1 models.py
1.2 视图
1.3 路由
2 jwt 多方式登录(auth的user表)
2.1 总结流程
2.2 序列化类
2.3 视图类
2.4 路由
补充base64
# 1 接口文档
编写规范:
-1 描述
-2 请求地址
-3 请求方式
-4 请求参数
-headers
-请求体
-请求参数
-5 请求编码格式
-6 返回格式
-示例
-返回数据字段含义
-其他:
-错误状态码
-...
-接口文档编写位置
-写在文件中:word,md,跟前端共享
-公司有接口文档平台
-yapi
-自研
-第三方接口文档:coreapi,drf-yasg
-自动生成---》访问项目地址 的 /doc 路径就能看到接口文档
# 2 jwt
-json web token的缩写,web方向前后端认证的方式,传统的认证方案使用session,使用jwt后,服务端不需要再存数据了,数据都放在客户端
-本质原理
-jwt 分三段:
-头:公司信息,加密方式,声明这是jwt
-荷载:数据,用户信息,用户id,名字,邮箱,过期时间
-签名:前面量部分通过某个加密方式加密得到一个签名,base64编码后拼接到最后
-开发中:
签发:登录,登录成功后,按照上面方式生成token串
认证:客户端携带token到后端,使用原来的加密方式把第一段和第二段再做加密,加密后跟传入的第三代比较,如果一样,说明传入的token可靠,如果不一样,说明数据被篡改,伪造的,不能信任,程序不能继续往后走了
----------------------------------------------------------------------------------
# 3 base64
-加密算法
-1 编码和解码,不加密:base64,url编码和解码
-2 摘要算法:md5,sha1 不能解开
-3 对称加密:des,AES 能加密,能解密;加密和解密使用同样的秘钥
-4 非对称加密 :RSA 能加密,能解密,加密秘钥和解密秘钥是不同的:公钥和私钥
-编码和解码使用内置模块
----------------------------------------------------------------------------------
# 4 django使用jwt
-django-rest-framework-jwt:有点老
-djangorestframework-simplejwt:新的
# 5 快速使用
-签发:path('login/', obtain_jwt_token),
-认证(局部使用,全局使用,局部禁用):
authentication_classes = [JSONWebTokenAuthentication,]
permission_classes = [IsAuthenticated]
1 jwt自定义表签发
1.1 models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
# 继承AbstractUser 直接使用自动签发token
# 纯自己写的用户表,需要自己签发
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
email = models.EmailField(max_length=32)
gender = models.IntegerField(choices=((1, '男'), (2, '女'), (0, '未知')))
1.2 视图
from django.shortcuts import render
from rest_framework.views import APIView
from .models import User
from rest_framework.response import Response
from rest_framework_jwt.settings import api_settings # drf的配置文件
# from rest_framework_jwt.utils import jwt_payload_handler
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
# rest_framework_jwt.utils.jwt_encode_handler
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
class UserView(APIView):
def post(self, request):
username = request.data.get('username')
password = request.data.get('password')
user = User.objects.filter(username=username, password=password).first()
if user:
# 签发token
# 1 通过user生成payload---》jwt 提供一个方法(username),传入user,返回payload
# payload = jwt_payload_handler(user)
payload={'username':'asdfasdf','exp':1694401763}
# 2 生成token---》jwt提供了方法,把payload放入--》token
token = jwt_encode_handler(payload)
return Response({'code': 101, 'msg': '登录成功', 'token': token, 'username': user.username})
else:
return Response({'code': 101, 'msg': '用户名或密码错误'})
1.3 路由
path('login/', UserView.as_view()),
2 jwt 多方式登录(auth的user表)
#1 用户名+密码 邮箱+密码 手机号+密码 都可以登录
username+password
email+password
phone+password
无论是username,email,phone都以 username形式提交到后端
于是:从username字段中取出来的,可能是用户名,可能是邮箱,可能是密码---》都能登录成功
#2 auth的user签发
#3 签发,校验用户 逻辑-----》放在序列化类中
# 5 存在一个问题---》已经迁移过表了---》已经存在auth的user表了,如果再去继承AbstractUser,再写用户表,就会出错
-解决方案:以后尽量不要这么做
-以后要扩写auth的user表,一开始就要扩写,不要等迁移完之后再扩写
-删库
-删迁移文件(不要删__init__.py和migrations文件夹)
-项目app的迁移文件
-django内置app的admin和auth的迁移文件
-重新迁移--两条命令
-扩写auth的user表,需要在配置文件配置 ###重要
# 6 创建超级用户--->创建到扩写的表 auth的user--》AuthUser
python manage.py createsuperuser
2.1 总结流程
# 序列化,反序列化,数据校验---》只用来做数据校验
# 前端传过来的字段,都要,而且要校验 :username password
# 只要视图类中执行 ser.is_valid():
会走字段自己的规则---》username过不了---》因为有unique---》所有需要重写
会走局部钩子---》这里没写
会走全局钩子---》全局钩子里校验
-分成了两个方法:好处是以后修改方法
-_get_user :多方式的 ,以后改成单方式登录,只要该这个方法即可
-_get_token:用的第三方签发,后期改成自己的签发,只需要改它即可
-把生成的token和用户名放到了,序列化类中,单是怕污染数据,放到了序列化类的对象的context中
# 视图类中取出token和username
token = ser.context.get('token')
username = ser.context.get('username')
2.2 序列化类
from .models import AuthUser
from rest_framework import serializers
import re
from rest_framework.exceptions import ValidationError
from rest_framework_jwt.settings import api_settings # drf的配置文件
# from rest_framework_jwt.utils import jwt_payload_handler
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
# rest_framework_jwt.utils.jwt_encode_handler
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
# 序列化类干什么? 只用来反序列化的校验
class LoginSerializer(serializers.ModelSerializer):
# username 是表中的字段---》自动映射过来的-->A user with that username already exists
# username有字段自己的规则---》唯一 unique---》去数据库查询发现有lqz,之间字段自己规则报错了,不会走到全局钩子
username=serializers.CharField() # 需要重写字段,不重写,字段自己规则过不了
class Meta:
model = AuthUser # 千万不要加逗号,程序运行不会报错,使用这个地方的时候,就报错了
fields = ['username', 'password']
# 在类内部 用 __ 开头表示隐藏
# 我们约定俗称,以 _ 开头的,表示只在类内部使用,不给外部用,但是万一外部要用,之间用既可以了
def _get_user(self, attrs):
# 用户名从哪拿? attrs是前端传入,校验过后的数据 {"username": "lqz","password": "lqz12345" }
username = attrs.get('username')
password = attrs.get('password')
if re.match(r'^1[3-9][0-9]{9}$', username): # 说明,用户提交的是手机号+密码
user = AuthUser.objects.filter(phone=username).first()
elif re.match(r'^.+@.+$', username): # (这个邮箱正则,不太准确) 如果是邮箱,说明用户提交的是 邮箱+密码
user = AuthUser.objects.filter(email=username).first()
else:
user = AuthUser.objects.filter(username=username).first()
if user and user.check_password(password):
return user
else:
# 在全局钩子中,只要校验不通过,就抛异常
# 不是return
raise ValidationError('用户名或密码错误')
def _get_token(self, user):
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return token
def validate(self, attrs):
# 1 校验用户
user = self._get_user(attrs) # 如果返回user,说明,用户名密码对了,如果没走到这里,说明抛异常,抛异常说明用户名密码错误
# 2签发token
token = self._get_token(user)
# 3 放在这里面 self 是序列化类的对象 ,context 是空字典,它是 视图类和序列化类之间沟通的桥梁
self.context['token'] = token
self.context['username'] = user.username
# 4 返回校验过后的数据
return attrs
2.3 视图类
## 2.基于auth的user表签发token,做多种方式登录
'''
用户名+密码 邮箱+密码 手机号+密码 都可以登录
username+password
email+password
phone+password
无论是username,email,phone都以 username形式提交到后端
于是:从username字段中取出来的,可能是用户名,可能是邮箱,可能是密码---》都能登录成功
'''
from rest_framework.viewsets import ViewSet, GenericViewSet
from rest_framework.decorators import action
import re
from .serializer import LoginSerializer
from .models import AuthUser
class UserView(GenericViewSet):
# 不需要做序列化或者反序列化,则可以不用写queryset
# queryset =
serializer_class = LoginSerializer
@action(methods=['POST'], detail=False)
def login(self, request, *args, **kwargs):
username = request.data.get('username')
password = request.data.get('password')
# 正则匹配,如果是手机号,要查手机号+密码;如果是邮箱,要查邮箱+密码;如果是其他,则查用户+密码
if re.match(r'^1[3-9][0-9]{9}$', username):
user = AuthUser.objects.filter(phone=username).first()
elif re.match(r'^.+@.+$', username):
user = AuthUser.objects.filter(email=username).first()
else:
user = AuthUser.objects.filter(username=username).first()
if user and user.check_password(password):
# 签发
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return Response({'code': 100, 'msg': '登录成功', 'token': token, 'username': username})
return Response({'code': 101, 'msg': '用户名或密码错误'})
-------------------优化版----------------------------------------------------
from rest_framework_jwt.views import obtain_jwt_token
## 复杂方式---》校验逻辑--》放在序列化类中
class UserView(GenericViewSet):
# queryset = 可以不写
serializer_class = LoginSerializer
@action(methods=['POST'], detail=False)
def login(self, request, *args, **kwargs):
# 拿到前端传入的用户名和密码,得到一个序列化类对象
ser = self.get_serializer(data=request.data)
if ser.is_valid(): # 字段自己(校验不过,因为username在数据库中有你传入的这个名字了),局部钩子,全局钩子---》需要在序列化类中写
# 校验完,并且签发完token了
# token从 序列化类的对象 取出来
# username从 序列化类的对象 取出来
# 现在不明白如何取出来的,假设取出来是对的---》因为在全局钩子中放入到了context中
token = ser.context.get('token')
username = ser.context.get('username')
return Response({'code': 100, 'msg': '登录成功', 'token': token, 'username': username})
else:
return Response({'code': 101, 'msg':'用户名密码错误'})
2.4 路由
from django.contrib import admin
from django.urls import path
from app01.views import UserView
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('user', UserView, 'user') # 127.0.0.1:8000/user/login 的post请求
urlpatterns = [
path('admin/', admin.site.urls),
# path('login/', UserView.as_view()),
]
urlpatterns += router.urls