2020-12-14 jwt多方式登录和表关系建立和表操作

上节回顾

1、jwt:重点(跟语言,框架无关)

-json web token
-cookie:客户端浏览器上的键值对,数据不安全
-session:服务端的键值对(内存,数据库,redis,文件),安全,对服务端压力大
-token:三段:头,荷载,签名
    -header:公司信息,加密方式,类型
    -payload:荷载,真正有用的数据部分,用户id,用户名字
    -signature:签名,把头和荷载部分通过加密算法加密--》得到一个签名

2、drf-jwt模块

-快速使用:默认使用的是auth的user表
    -1、创建用户
    -2、在路由中配置path('login/',obtain_jwt_token),
    -3、在postman中测试,用户名密码输入刚刚创建的用户就可以生成token
    -4、让一个视图必须登录以后才能访问
        -authentication_classes=[JSONWebTokenAuthentication,]
        -permission_classes=[IsAuthenticated,]
    -5、让一个视图可以登录后访问,也可以不登陆访问就只加上token的认证
        -authentication_classes=[JSONWebTokenAuthentication,]
    -6、用postman测试,在请求头中加入
        -Authorization   jwt adasffa
-自己写基于jwt的认证类(登录了能访问,不登录就不能访问)
    class JwtAuthentication(BaseJSONWebTokenAuthentication):
        def authenticate(self, request):
            #token=request.GET.get('token')
            token=request.META.get('HTTP_Authorization'.upper())
            try:
                # 验证token是否正确
                payload = jwt_decode_handler(token)
            except jwt.ExpiredSignature:
                raise AuthenticationFailed('过期了')
            except jwt.DecodeError:
                raise AuthenticationFailed('解码错误')
            except jwt.InvalidTokenError:
                raise AuthenticationFailed('不合法的token')
            user=self.authenticate_credentials(payload)  # 这是用的自带的user表的字段取出的user,不适用于自己写的user表,自己创建的用户表需要自己手动取
            return (user, token)
-自定制认证类的使用方式:
    -全局使用
    -局部使用

3、base64编码(跟语言无关,跟框架无关)

-不同语言的base64可以互相编码解码
-base64内置模块
-图片的二进制,有时候也会以base64的形式编码

4、drf的视图(两个视图基类,5个视图扩展类,9个视图子类,视图集)+序列化器+自动生成路由

今日内容

1、基于jwt的多方式登录

-1、手机号+密码,用户名+密码,邮箱+密码
-2、流程分析(post请求):
    -路由:自动生成
    -视图类:ViewSet(ViewSetMixin,Views.APIView) 所有东西自己写,路由可以自己生成
    -序列化类:重写validate方法,在这里面对用户名和密码进行校验,就是全局钩子
-3、代码实现

路由

也可以选择自动生成
path('login/', views.LoginViewSet.as_view({'post':'create'})),

视图

class LoginViewSet(ViewSet):
    def create(self, request, *args, **kwargs):
        # 实例化得到一个序列化类的对象
        # ser=LoginSerializer(data=request.data,context={'request':request}) #context数据交流的桥梁
        ser = LoginSerializer(data=request.data)
        # 序列化类的对象的校验方法
        ser.is_valid(raise_exception=True)  # 字段自己的校验,局部钩子校验,全局钩子校验,只要走了这个,并且没有抛异常,
        #就会走到序列化器的全局钩子去校验,那么全局钩子里往序列化器里添加属性,那么我在这数据校验之后就能从序列化对象里取到添加的属性
        # 如果通过,表示登录成功,返回手动签发的token
        token = ser.context.get('token')
        username = ser.context.get('username')
        return APIResponse(token=token, username=username)
        # 如果失败,不用管了

序列化器类

from rest_framework import serializers
from app01.models import UserInfo
import re
from rest_framework.exceptions import ValidationError
from rest_framework_jwt.utils import jwt_encode_handler, jwt_payload_handler
from rest_framework_jwt.views import obtain_jwt_token

class LoginSerializer(serializers.ModelSerializer):
    username = serializers.CharField()

    class Meta:
        model = UserInfo
        fields = ['username', 'password']

    def validate(self, attrs):
        # username可能是邮箱,手机号,用户名
        username = attrs.get('username')
        password = attrs.get('password')
        # 如果是手机号
        if re.match('^1[3-9]\d{9}$', username):
            # 以手机号登录
            user = UserInfo.objects.filter(phone=username).first()
        elif re.match('^.+@.+$', username):
            # 以邮箱登录
            user = UserInfo.objects.filter(email=username).first()
        else:
            # 以用户名登录
            user = UserInfo.objects.filter(username=username).first()
        # 如果user有值并且密码正确
        if user and user.check_password(password):
            # 登录成功,生成token
            # drf-jwt中有通过user对象生成token的方法
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            # token是要在视图类中使用,现在我们在序列化类中
            # self.context.get('request')
            # 视图类和序列化类之间通过context这个字典来传递数据
            self.context['token'] = token
            self.context['username'] = user.username
            return attrs

        else:
            raise ValidationError('用户名或密码错误')

2、 自定义user表,签发token,认证类

表模型

class MyUser(models.Model):
    # 这里字段必须用username,应为是生成token的源码里用的就是这个字段名
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)
    phone = models.CharField(max_length=32)
    email = models.EmailField()

路由

path('login2/', views.MyLoginView.as_view()),

视图

# 这是从jwt的配置文件中导出的功能
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
from rest_framework_jwt.views import obtain_jwt_token


class MyLoginView(APIView):
    def post(self, request, *args, **kwargs):
        username = request.data.get('username')
        password = request.data.get('password')
        # 如果是手机号
        if re.match('^1[3-9]\d{9}$', username):
            # 以手机号登录
            user = MyUser.objects.filter(phone=username).first()
        elif re.match('^.+@.+$', username):
            # 以邮箱登录
            user = MyUser.objects.filter(email=username).first()
        else:
            # 以用户名登录
            user = MyUser.objects.filter(username=username).first()
        # 如果user有值并且密码正确
        if user and user.password == password:
            # 登录成功,生成token
            # drf-jwt中有通过user对象生成token的方法
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            return APIResponse(token=token, username=user.username)
        else:
            return APIResponse(code=101, msg='用户名或密码错误')

from app01.auth import JWTAuthentication

class OrderAPIView(APIView):
    authentication_classes = [JWTAuthentication,]
    def get(self,request):
        # print(request.user)  # 自己的user对象 {'user_id': 1, 'username': 'lqz', 'exp': 1605545489, 'email': '[email protected]'}
        print(request.user)  # user是个字典,就是认证后返回的user,就是payload字典。内部有user_id,
        #后续要查询该用户的所有订单,直接根据user_id查询即可
        return APIResponse(msg='全部订单')

自定义认证类

from rest_framework_jwt.utils import jwt_decode_handler

import jwt
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication

from app01.models import MyUser


class JWTAuthentication(BaseJSONWebTokenAuthentication):
    def authenticate(self, request):
        token = request.META.get('http_authorization'.upper())
        try:
            payload = jwt_decode_handler(token)
        except jwt.ExpiredSignature:
            raise AuthenticationFailed('过期了')
        except jwt.DecodeError:
            raise AuthenticationFailed('解码错误')
        except jwt.InvalidTokenError:
            raise AuthenticationFailed('不合法的token')
        # 得到user对象,应该是自己user表中的user对象
        print(payload)  # {'user_id': 1, 'username': 'lqz', 'exp': 1605545489, 'email': '[email protected]'}
        # user=MyUser.objects.get(id=payload['user_id'])  # 可以这样取,是访问数据库,效率有问题,也可以直接从那个payload里取,就是字典里取值,很快
        user = payload  # 直接让uesr就是payload
        return (user, token)

3、book,publish,author表关系及抽象表建立

# 注意:以后所有的数据删除,尽量用软删除,使用一个字段标志是否删除,而不是真正的从数据库中删除
    -好处:1 这样删除数据不会影响索引,不会导致索引失效
          2 之前存的用户数据还在,以备以后使用
# 表模型如下
# 抽象出一个基表(不再数据库生成,abstract=True),只用来继承

class BaseModel(models.Model):
    is_delete = models.BooleanField(default=False)
    create_time = models.DateTimeField(auto_now_add=True)

    class Meta:
        # 基表必须设置abstract,基表就是给普通Model类继承使用的,设置了abstract就不会完成数据库迁移完成建表
        abstract = True
class Book(BaseModel):
    name = models.CharField(max_length=16)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    publish = models.ForeignKey(to='Publish', db_constraint=False, on_delete=models.DO_NOTHING)
    # 重点:多对多外键实际在关系表中,ORM默认关系表中两个外键都是级联
    # ManyToManyField字段不提供设置on_delete,如果想设置关系表级联,只能手动定义关系表
    authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False)

    # 自定义连表深度,不需要反序列化,因为自定义插拔属性不参与反序列化
    # 把他包装成数据属性,这样在序列化类中可以显示序列化后的字段里有这个属性,不用重写字段
    @property
    def publish_name(self):
        return self.publish.name

    # 这个写到序列化器里后和source一样,如果是个可执行的就会执行,如果是对象就会拿过去,包不包装成数据属性都一样
    @property
    def author_list(self):
        # ll=[]
        # for author in self.authors.all():
        #     ll.append({'name':author.name,'sex':author.get_sex_display()})
        return [{'name': author.name, 'sex': author.get_sex_display()} for author in self.authors.all()]


class Publish(BaseModel):
    name = models.CharField(max_length=16)
    address = models.CharField(max_length=64)


class Author(BaseModel):
    name = models.CharField(max_length=16)
    sex = models.IntegerField(choices=[(0, '男'), (1, '女')], default=0)


class AuthorDetail(BaseModel):
    mobile = models.CharField(max_length=11)
    # 有作者可以没有详情,删除作者,详情一定会被级联删除
    # 外键字段为正向查询字段,related_name是反向查询字段
    author = models.OneToOneField(to='Author', related_name='detail', db_constraint=False, on_delete=models.CASCADE)

4、book表单增群增

class BookView(APIView):
    def post(self, request, *args, **kwargs):
        if isinstance(request.data, dict):
            # 增一条
            ser = serializer.BookSerializer(data=request.data)
            ser.is_valid(raise_exception=True)
            ser.save()
            return APIResponse(data=ser.data)
        elif isinstance(request.data, list):
            # 增多条
            ser = serializer.BookSerializer(data=request.data, many=True)
            # 内部如何实现的?
            # many=True,ser不是BookSerializer对象,而是ListSerializer对象,套了一个个的BookSerializer
            print(type(ser))
            ser.is_valid(raise_exception=True)
            #
            from rest_framework.serializers import ListSerializer
            ser.save()  # ListSerializer的save
            return APIResponse(msg='增加%s条成功' % len(request.data))

5、book表单查群查

class BookView(APIView):
    def get(self, request, *args, **kwargs):
        pk = kwargs.get('pk', None)# 这里要设置个默认值,因为有可能kwargs是个空,空去get就会报错
        if pk:
            # 单查
            # 方式一
            # book=models.Book.objects.filter(id=pk).filter(is_delete=False).first()
            # if not book:
            #     raise Exception('要查询的不存在')

            # 方式二
            book = models.Book.objects.get(id=pk, is_delete=False)
            ser = serializer.BookSerializer(instance=book)

        else:
            # 查所有
            book_list = models.Book.objects.all().filter(is_delete=False)
            ser = serializer.BookSerializer(instance=book_list, many=True)
        return APIResponse(data=ser.data)

6 book表单改群改

class BookView(APIView):
    def put(self, request, *args, **kwargs):
        pk = kwargs.get('pk', None)
        if pk:
            # 单条修改
            book = models.Book.objects.get(id=pk, is_delete=False)
            ser = serializer.BookSerializer(instance=book, data=request.data)
            ser.is_valid(raise_exception=True)
            ser.save()
            return APIResponse(msg='修改成功')
        else:
            # 分析:ListSerializer的update方法没有写,需要我们自己写
            from rest_framework.serializers import ListSerializer
            # pks=[item['id'] for item in request.data] # 需要把id剔除

            # 如果不重写ListSerializer的update方法,这是存不进去的
            pks = []
            for item in request.data:
                pks.append(item['id'])
                item.pop('id')

            print(request.data)
            book_list = models.Book.objects.filter(id__in=pks, is_delete=False)
            ser = serializer.BookSerializer(instance=book_list, data=request.data, many=True)
            print(type(ser))
            ser.is_valid(raise_exception=True)
            ser.save()
            return APIResponse(msg='修改%s条成功')

            # 你们能想到的方法
            # pks = []
            # for item in request.data:
            #     pks.append(item['id'])
            #     item.pop('id')
            # book_list = models.Book.objects.filter(id__in=pks, is_delete=False)
            #
            # for i,book in enumerate(book_list):
            #     ser = serializer.BookSerializer(instance=book, data=request.data[i])
            #     ser.is_valid(raise_exception=True)
            #     ser.save()
            # return APIResponse(msg='修改%s条成功'%len(book_list))

7 book表的单删群删

class BookView(APIView):

    def delete(self, request, *args, **kwargs):
        pk = kwargs.get('pk', None)
        pks = []
        if pk:
            # 单条删除
            # res=models.Book.objects.filter(id=pk).update(is_delete=True)
            # print(res)
            # return APIResponse(msg='删除成功')
            pks.append(pk)
        else:
            pks = request.data  # 传过来的就是列表

        res = models.Book.objects.filter(id__in=pks).update(is_delete=True)
        if res >= 1:
            return APIResponse(msg='删除%s条成功' % res)
        else:
            # raise Exception('没有要删除的数据')
            return APIResponse(code=999, msg='没有要删除的数据')

8 序列化类

from app01 import models

# 重写群改的序列化类,因为ListSerializer里没有重写update方法
class ListBookSerializer(serializers.ListSerializer):
    # def create(self, validated_data):
    #     print('=======',validated_data)
    #     return '1'
    def update(self, instance, validated_data):
        print(instance) # book_list:是一堆图书对象
        print(validated_data) # 列表套字典,是要修改的数据

        #  self.child就是BookSerializer
        return [self.child.update(book, validated_data[i]) for i, book in enumerate(instance)]


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Book
        list_serializer_class=ListBookSerializer # 指定many=True的时候,生成的ListBookSerializer的对象了
        fields = ['name', 'price', 'publish', 'authors', 'publish_name', 'author_list']
        extra_kwargs = {
            'publish': {'write_only': True},
            'authors': {'write_only': True},
            'publish_name': {'read_only': True},# 后面两个字段是模型类的数据属性,起始也可以重写两个字段也能达到相同的效果,但是这样写更巧妙
            'author_list': {'read_only': True},

        }

    # def create(self, validated_data): # 写了就不走自己的保存了
    #     print(validated_data)

9、路由

path('books/', views.BookView.as_view()),
re_path('books/(?P\d+)', views.BookView.as_view()),

你可能感兴趣的:(2020-12-14 jwt多方式登录和表关系建立和表操作)