DRF | 入门第十天 JWT签发

79 阅读4分钟

JWT自定义表的签发

from django.contrib.auth.models import AbstractUser

继承AbstractUser, 直接使用自动签发Token

自定义用户表

纯自己写的用户表,需要自己签发(必须要有username的字段)

from django.shortcuts import render  
from rest_framework.views import APIView  
from app01.models import User  
from rest_framework.response import Response  
from rest_framework_jwt.settings import api_settings # DRF的配置文件  
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER  
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER  
  
# from rest_framework_jwt.utils import jwt_encode_handler  
  
# Create your views here.  
  
import base64  
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:  
            # 签发  
            # 1. 通过user生成payload,JWT提供一个方法(username),传入user,返回payload  

            payload = jwt_payload_handler(user)  
            # 2. 生成token,JWT提供了一个方法,把payload放入token  
            token = jwt_encode_handler(payload)  

            return Response({'code': 200, 'msg': "登录成功", 'token': token, 'username': user.username})  
        else:  
            # 用户登录失败  
            return Response({'code': 1000, 'msg': "用户名或者密码错误"})

JWT 多方式登录(本次使用auth的user表)

签发,校验用户 逻辑 放在序列化类中 实现:用户+密码,手机号+密码,邮箱+密码,均可实现登录

会遇到的问题:当前已经存在user表,我们再去继承Auth的user表,会出错

解决办法:

 一开始就要把Auth的user表定制好
 删除库,都要删(删库,删迁移文件,删除Django内置APP的Admin和Auth的迁移文件
 (不要删除__init__.py)
 

setting.py里面配置AUTH_USER_MODEL = 'app01.AuthUser'

run manage.py里面makemigrationmigrate两条指令

model.py里面我们导入from django.contrib.auth.models import AbstractUser 随意在表里面添加一个扩充字段试一下

class AuthUser(AbstractUser):  
    # 默认有username,password,Email等字段  
    # 我们要扩写字段  
    phone = models.CharField(max_length=32)

image.png

成功添加进去了,接下来我们尝试创建一个超级用户createsuperuser。根据提示添加信息

image.png 也成功添加超级用户!

接下来在视图层里面 首先我们要通过正则匹配然后如果查询到同理做签发

if re.match(r'^1[3-9][0-9]{9}$', username):  
user = AuthUser.objects.filter(phone=username, password=password).first()  
return Response({'code': 200, 'msg': "登录成功", 'token': token, 'username': user.username})  
elif re.match(r'^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$', username):  
user = AuthUser.objects.filter(email=username, password=password).first()  
return Response({'code': 200, 'msg': "登录成功", 'token': token, 'username': user.username})  
else:  
user = AuthUser.objects.filter(username=username, password=password).first()  
return Response({'code': 200, 'msg': "登录成功", 'token': token, 'username': user.username})

完整代码:


# 基于Auth的user表,我们自己签发token,同时做多种方式登录  
from rest_framework.viewsets import ViewSet, GenericViewSet  
from app01.serializer import LoginSerializer  
from rest_framework.decorators import action  
import re  
  
class UserView(GenericViewSet):  
    # 从前端传回来的不论是用户名,电话还是邮箱,我们都以username的参数传进来然后通过正则表达式再做username的类型判断  
    # queryset = 可以不写  
    serializer_class = LoginSerializer  

    @action(methods=['POST'], detail=False, url_path='login')  
    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'^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$', username):  
            user = AuthUser.objects.filter(email=username).first()  
        else:  
            user = AuthUser.objects.filter(username=username).first()  

        # 如果查询到此用户并且都能用户名密码都能匹配上  
        if user and user.check_password(password):  
        # 签发  
        # 1. 通过user生成payload,JWT提供一个方法(username),传入user,返回payload  
            payload = jwt_payload_handler(user)  
            # 2. 生成token,JWT提供了一个方法,把payload放入token  
            token = jwt_encode_handler(payload)  
            return Response({'code': 200, 'msg': "登录成功", 'token': token, 'username': user.username})  
        else:  
            # 用户登录失败  
            return Response({'code': 1000, 'msg': "用户名或者密码错误"})

上面我们需要注意的一点是我们不要用明文密码去和数据库里面加密后的密码匹配

from django.contrib.auth.hashers import check_password, make_password

user.check_password(password)

JWT多方式登录(复杂版+钩子)

serializer.py

这里面要注意的是,在Auth的user表里面username字段是unique,所以我们需要在序列化类里面重新写这个字段

username = serializers.CharField()

然后这里面的def _get_user(self, attrs):def _get_token(self, user): 为了扩展性更高。单_是墨守成规的只在类内部使用

from app01.models import AuthUser  
from rest_framework import serializers  
import re  
from django.contrib.auth.hashers import check_password  
  
from rest_framework_jwt.settings import api_settings # DRF的配置文件  
from rest_framework.exceptions import ValidationError  
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER  
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER  
  
# 写这个序列化类只是为了做返序列化的校验  
class LoginSerializer(serializers.ModelSerializer):  
    username = serializers.CharField()  
    class Meta:  
    model = AuthUser  
    fields = ['username', 'password']  

    def _get_user(self, attrs):  
        # 用户名从哪里拿?attrs是前端传入,校验过后的数据{"username":"JJ", "password":"123123"}  
        username = attrs.get('username')  
        password = attrs.get('password')  
        print(username, password)  

        if re.match(r'^1[3-9][0-9]{9}$', username):  
            user = AuthUser.objects.filter(phone=username).first()  
        elif re.match(r'^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$', 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:  
            # 在全局钩子中,只要校验不通过,就抛异常  
            raise ValidationError('用户名或者密码错误')  

    def _get_token(self, user):  
        payload = jwt_payload_handler(user)  
        token = jwt_encode_handler(payload)  
        return token  

    def validate(self, attrs):  
        user = self._get_user(attrs) # 如果能拿到user,说明用户名和密码对了,如果没有走到这里,说明被抛异常了,抛异常:用户名或者密码错误  
        token = self._get_token(user)  
        self.context['token'] = token  
        self.context['username'] = user.username  
        print(attrs)  
        return attrs

views.py

class UserView(GenericViewSet):  
# queryset = 可以不写  
serializer_class = LoginSerializer  
  
@action(['POST'], detail=False)  
def login(self, request, *args, **kwargs):  
# 直接拿到前端传入的数据,实例化得到一个对象  
ser = self.get_serializer(data=request.data)  
if ser.is_valid(): # 字段自己,局部钩子,全局钩子  
# 校验完,并且签发完用户token  
# token 从序列化的对象里面取出来  
# username 从序列化的对象里面取出来  
  
'''建议放在ser.context里面,这样不容易污染数据'''  
  
token = ser.context.get('token')  
username = ser.context.get('username')  
return Response({'code': 200, 'msg': "登录成功", 'token': token, 'username': username})  
else:  
return Response({'code':1000, 'msg':ser.errors})