drf - jwt自定义表签发、jwt 多方式登录(auth的user表)

发布时间 2023-09-22 12:41:16作者: Way*yy

jwt自定义表签发

1、导入模块:
	from rest_framework_jwt.settings import api_settings
2、写一个属性:
    jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
    jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
3、登录逻辑:
    class UserViews(ViewSet):
        @action(methods=["POST"], detail=False)
        def login(self, request, *args, **kwargs):
            username = request.data.get("username")
            password = request.data.get("password")
            user = User.objects.filter(username=username, password=password).first()
            print(user)
            if user:
                # 通过user生成payload----->jwt提供的方法,但是用户名的字段必须是username,传入user,生成payload
                payload = jwt_payload_handler(user)
                # 生成token ----->jwt提供了方法,把payload传入------>生成token
                token = jwt_encode_handler(payload)
                return Response({"code": 100, "msg": "登录成功", "username": user.username, "token": token})
            else:
                return Response({"code": 101, "msg": "登录失败,账号或密码输入错误"})

路由层

from django.urls import path, include
from app_one.views import UserViews
from rest_framework.routers import SimpleRouter

render = SimpleRouter()
render.register("users", UserViews, "users")

urlpatterns = [
    path('admin/', admin.site.urls),

    path('', include(render.urls)),
]

总结

基于自定义的用户表,签发token:
    -1、前端----->发起http请求,携带用户的用户名和密码----->到后端
    -2、后端从request.data中取出前端传入的数据----->字典格式------>取出用户名和密码
    -3、拿着用户名和密码去数据库中查询有没有该用户
    -4、如果有签发token
        -4.1、通过当前用户得到payload(自己生成荷载)
        -4.2、通过荷载生成token
    -5、返回给前端{"code":100,"msg":"登录成功","token":token,"username":user.username}
    -6、如果查不到该用户,返回用户名或密码输入错误

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

1、用户名+密码、邮箱+密码、手机号+密码  都可以登录
    username+password、email+password、phone+password
    无论是username,email,phone都以 username形式提交到后端
    于是:从username字段中取出来的,可能是用户名,可能是邮箱,可能是密码--->都能登录成功
2、auth的user签发
3、签发、校验用户逻辑------>放在序列化类中
4、存在一个问题--->已经迁移过表了--->已经存在auth的user表了,如果再去继承AbstractUser,再写用户表,就会出错
	-解决方案:以后尽量不要这么做
    	-以后要扩写auth的user表,一开始就要扩写,不要等迁移完之后再扩写
    -删库
    -删迁移文件(不要删__init__.py和migrations文件夹)
    	-项目app的迁移文件
        -django内置app的admin和auth的迁移文件
    
    -重新迁移--两条命令
    -扩写auth的user表,需要在配置文件配置 ###重要
5、创建超级用户----->创建到扩写的表中 auth的user-->AuthUser
	python  manage.py createsuperuser
面条版
from rest_framework.viewsets import ViewSet, GenericViewSet
from rest_framework.decorators import action
from rest_framework_jwt.settings import api_settings
from rest_framework.response import Response
from .serializer import UserSerializer
from .models import AuthUser
import re
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
class AuthUserView(GenericViewSet):
    serializer_class = UserSerializer

    @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).fister()

        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": user.username})
        else:
            return Response({"code": 101, "msg": "登录失败,账号或密码失败"})
封装版本:视图层
from rest_framework.viewsets import ViewSet, GenericViewSet
from rest_framework.decorators import action
from rest_framework_jwt.settings import api_settings
from rest_framework.response import Response
from .serializer import UserSerializer
from .models import AuthUser

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

class AuthUserView(GenericViewSet):
    serializer_class = UserSerializer

    @action(methods=["POST"], detail=False)
    def login(self, request, *args, **kwargs):
        # 拿到前端传入的用户名和密码,得到一个序列化类对象
        user_serializer = self.get_serializer(data=request.data)
        if user_serializer.is_valid():
            token = user_serializer.context.get("token")
            username = user_serializer.context.get("username")
            return Response({"code": 100, "msg": "登录成功", "token": token, "username": username})
        else:
            return Response({"code": 101, "msg": "账号或密码输入错误"})
序列化类
from rest_framework import serializers
from .models import AuthUser
from rest_framework_jwt.settings import api_settings
from rest_framework.exceptions import AuthenticationFailed
import re

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER


class UserSerializer(serializers.ModelSerializer):
    # 需要重写字段,不重写,字段自己规则过不了
    username = serializers.CharField()

    class Meta:
        model = AuthUser
        fields = ["username", "password"]
        # username有字段自己的规则--->唯一 unique--->去数据库查询发现有lqz,之间字段自己规则报错了,不会走到全局钩子

    def _get_user(self, attrs):
        # 从校验的数据中取出username、password
        username = attrs.get("username")
        password = attrs.get("password")
        # 通过正则来匹配我的username是手机号还是邮箱还是用户名
        if re.match(r'^1[3-9][0-9]{9}$', username):
            user = AuthUser.objects.filter(phone=username).fister()

        elif re.match(r'^.+@.+$', username):
            user = AuthUser.objects.filter(email=username).first()

        else:
            user = AuthUser.objects.filter(username=username).first()
        # 如果我的用户对象以及我加密后的密码都为True,将这个user对象返回
        if user and user.check_password(password):
            return user
        # 否则主动抛出异常
        else:
            raise AuthenticationFailed("账号或密码输入错误")

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

    def validate(self, attrs):
        # 如果返回user,说明,用户名密码对了,如果没走到这里,说明抛异常,抛异常说明用户名密码错误
        user = self._get_user(attrs)
        token = self._get_token(user)
        # 放在这里面  self 是序列化类的对象  ,context 是空字典,它是  视图类和序列化类之间沟通的桥梁
        self.context["username"] = user.username
        self.context["token"] = token
        return attrs

路由层
from django.contrib import admin
from django.urls import path, include
from app_one.views import AuthUserView
from rest_framework.routers import SimpleRouter

router = SimpleRouter()
router.register("user", AuthUserView, "user")

urlpatterns = [
    path('admin/', admin.site.urls),
]
urlpatterns += router.urls

总结

# 序列化,反序列化,数据校验--->只用来做数据校验
# 前端传过来的字段,都要,而且要校验 :username  password
# 只要视图类中执行 ser.is_valid():
    会走字段自己的规则--->username过不了--->因为有unique--->所有需要重写
    会走局部钩子--->咱们没写
    会走全局钩子--->全局钩子里校验
    	-分成了两个方法:好处是以后修改方法
        -_get_user :多方式的 ,以后改成单方式登录,只要该这个方法即可 
        -_get_token:用的第三方签发,后期改成自己的签发,只需要改它即可
        
  	-把生成的token和用户名放到了,序列化类中,单是怕污染数据,放到了序列化类的对象的context中
        -self.context["username"] = user.username
        -self.context["token"] = token
    
# 视图类中取出来
	 token = ser.context.get('token')
     username = ser.context.get('username')

作业

# 1 自定义用户表,普通签发
# 2 扩写authuser表,多方式登录
# 3 自己的用户表,多方式登录

# -----自定义认证类-----