drf-jwt自定义表签发、多方式登录

发布时间 2023-09-11 19:22:40作者: Maverick-Lucky

一、jwt自定义表签发

自定义表签发,用的是自己定义的表

1. models.py:

  - 注意点:因为视图中使用了drf-jwt的自动签发,所以用户名必须为username

from django.db import models
# 自定义签发用的是自定义的user表
# 注意点:使用drf-jwt的自动签发,用户名必须是username
class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)
    email = models.EmailField(max_length=32)
    phone = models.CharField(max_length=32)
    gender = models.IntegerField(choices=((1, ''), (2, ''), (0, '未知')))

2. views.py:

from rest_framework.views import APIView
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
from .models import User

# 自定义签发表签发
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签发
            # 通过user生成payload,jwt中有提供一个方法:
            payload = jwt_payload_handler(user)
            # jwt提供的方法:通过payload生成token
            token = jwt_encode_handler(payload)
            return Response({'code': 100, 'msg': '登录成功', 'token': token, 'username': user.username})
        else:
            return Response({'code': 101, 'msg': '用户名或者密码错误'})

drf配置文件:

 3. urls.py:

from django.contrib import admin
from django.urls import path
from app01.views import UserView
urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/',UserView.as_view()), # 登录对应的路由
]

4. postman测试结果:

  - 发送POST请求,请求地址:http://127.0.0.1:8000/login/,在请求体输入用户名和密码

 二、 jwt多方式登录(auth的user表)

1.登录方式:

  - 用户名+密码

  - 邮箱+密码

  - 手机号+密码

这几种方式都可以登录,无论是username,email,phone都以usernam的形式提交到后端。

于是:从username字段中取出来的,可能是用户名,可能是邮箱,可能是密码,都能登录成功

2. auth的user签发

3. 签发,校验用户的逻辑,放在序列化类中

  这个序列化类只用来进行对数据的校验(反序列化校验)

4. 存在一个问题:

  当已经迁移过表了,就已经存在auth_user表了,如果在去继承AbstractUser,在写用户表,就会出错

  - 解决方案:以后尽量不要这么做

    - 以后扩写auth的User表,在迁移表之前进行扩写

    - 删库

    - 删迁移文件(不要删__init__.py和migrations文件夹)

      - 项目app的迁移文件

      - django内置app的admin和auth的迁移文件migrations文件中的记录,除了init

    - 重新迁移--两条命令

    - 扩写auth的user表,需要在配置文件配置  

5. 创建超级用户 :创建到扩写的表(authuser表)

  - 创建超级用户:python manage.py createsuperuser

多方式登录的流程:

 序列化类:只用来做数据校验

  - 前端传过来的字段都要校验:username、password

 视图类中执行 ser.is_valid():

  -首先会走字段自己的规则,username过不了,因为有unique:它是从表中映射过来的,只要校验,就会去表中查有没有和前端提交的username一样的,所以要重写username字段

  - 会走局部钩子(没有写)

  - 会走全局钩子,在全局钩子中进行校验

    - 写了两个方法:好处是以后有什么变化,只需要修改方法

     - _get_user:多方式登录校验,以后改成单方式登录,只要修改这个方法即可

      在类的内部,用__开头表示隐藏,我们约定俗成,以_开头,表示只在类内部使用,不给外部使用,但是万一外部要用,直接用就可以了

    - _get_token:用的第三方签发,后期改成自己的签发,我们只需要修改这个方法即可

    - 把生成的token和用户名放到了序列化类中,但是怕污染数据,放到了序列化类的对象的context中

   - 从视图类中取出来

    token = ser.context.get('token')

    username = ser.context.get('username')

1. 多方式登录签发的简单方法:

models.py:

 

from django.contrib.auth.models import AbstractUser
class AuthUser(AbstractUser):
    # 默认有:username,password,email,。。。。
    # 扩写字段
    phone = models.CharField(max_length=32)

 

views.py:

# 基于auth的user表,多方式登录签发---简单方式
# 继承GenericViewSet,自己签发token,做多方式登录#####  写序列化类
from rest_framework.viewsets import GenericViewSet
from .serializer import LoginSerializer
from rest_framework.response import Response
from rest_framework.decorators import action
import re
from .models import AuthUser


class UserView(GenericViewSet):
    serializer_class = LoginSerializer

    @action(methods=['POST'], detail=False)
    def login(self, requset,*args,**kwargs):
        username = requset.data.get('username')
        password = requset.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()
        # 查询用户名和密码是否正确
        # 不能直接拿明文密码和密文密码进行比较,所以用check_password来比较
        if user and user.check_password(password):
            # 校验用过,签发token
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            return Response({'code': 100, 'msg': '登录成功', 'token': token, 'username': user.username})
        else:
            return Response({'code': 101, 'msg': '用户名或者密码错误'})

urls.py:

from django.contrib import admin
from django.urls import path,include
from app01.views import UserView
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('user',UserView,'user')
urlpatterns = [
    path('admin/', admin.site.urls),
    path('',include(router.urls)) # 多方式登录签发路由
]

serializer.py:

from rest_framework import serializers
from .models import AuthUser
class LoginSerializer(serializers.ModelSerializer):
    class Meta:
        model = AuthUser
        fields = ['username','password']

postman测试结果:

  - 请求方式:POST,请求地址:http://127.0.0.1:8000/user/login/

  - 用户名+密码

  - 邮箱+密码:

 

 2. 多方式登录签发的复杂方式

将用户登录方式的校验放在序列化类中进行校验

models.py:

from django.contrib.auth.models import AbstractUser
class AuthUser(AbstractUser):
    # 默认有:username,password,email,。。。。
    # 扩写字段
    phone = models.CharField(max_length=32)

views.py:

# 基于auth的user表,多方式登录签发---复杂方式
from rest_framework.viewsets import GenericViewSet
from .serializer import LoginSerializer
from rest_framework.response import Response
from rest_framework.decorators import action
class UserView(GenericViewSet):
    serializer_class = LoginSerializer
    @action(methods=['POST'],detail=False)
    def login(self,requset,*args,**kwargs):
        # 从前端获取提交的数据,进行序列化得到序列化对象
        ser = self.get_serializer(data=requset.data)
        if ser.is_valid(): # 进行反序列化校验
            # 校验通过则取出token,和username
            username = ser.context.get('username')
            token = ser.context.get('token')
            return Response({'code':100,'msg':'登录成功','token':token,'username':username})
        else:
            return Response({'code':101,'msg':'用户名或者密码错误'})

serializer.py:

from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework_jwt.settings import api_settings
# rest_framework_jwt.utils.jwt_encode_handler
jwt_encode_handler= api_settings.JWT_ENCODE_HANDLER
# rest_framework_jwt.utils.jwt_payload_handler
jwt_payload_handler=api_settings.JWT_PAYLOAD_HANDLER
from .models import AuthUser
import re
class LoginSerializer(serializers.ModelSerializer):
    # 因为Username是直接从auth的user表中映射过来的,
    # username 有自己的字段规则:unique=True ,当前端提交的username和数据库中的一样时,就会报错
    # 因此要重写这个username字段
    username = serializers.CharField()
    class Meta:
        model = AuthUser
        fields = ['username','password']
    # 判断是哪个登录的方式
    def _get_user(self,attrs):
        # attrs 是前端传入,校验过后的数据  {"username": "hh","password": "123" }
        username = attrs.get('username')
        password = attrs.get('password')
        if re.match(r'^1[3-9][0-9]{9}$',username):
            # 说明是phone+password的登录方式
            user = AuthUser.objects.filter(phone=username).first()
        elif re.match(r'^.+@.+$',username):
            # 说明是email+password的登录方式
            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('用户名或者密码错误')

    # 校验通过就签发token
    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) # 用户名和密码验证通过,则签发token

        # 将username和token放入ser中,通过context天骄,context是空字典
        self.context['username']=user.username
        self.context['token']=token
        return attrs

urls.py:

from django.contrib import admin
from django.urls import path,include
from app01.views import UserView
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('user',UserView,'user')
urlpatterns = [
    path('admin/', admin.site.urls),
    path('',include(router.urls)) # 多方式登录签发路由
]

postman测试:  

  - 请求方式:POST,请求地址:http://127.0.0.1:8000/user/login/

- 邮箱+密码:

- 用户名+密码: