Django之REST_FRAMEWORK 认证组件

时间:2021-03-15 14:27:55

Django之DRF之认证组件

# from rest_framework.views import APIView
# APIView 中的  dispatch  中 执行的  self.initial(request,*args,**kwargs)中的 
# APIView---->dispatch------>self.initial------>三个校验
        # self.perform_authentication(request)  # 认证校验
        # self.check_permissions(request)  # 权限校验
        # self.check_throttles(request)  # 频率校验
# 这里的self 指的是我写的那个序列化类

    def perform_authentication(self, request):
        # 这里的request 是新的request  原生的request 在   新request._request 中
        """
        Perform authentication on the incoming request.

        Note that if you override this and simply 'pass', then authentication
        will instead be performed lazily, the first time either
        `request.user` or `request.auth` is accessed.
        """
        # 返回的user 是 新的request 中的一个 被伪装成属性的方法(@property)
        # 想要超看这个 需要导包  from rest_frmework.request import Request
        request.user

    def check_permissions(self, request):
        """
        Check if the request should be permitted.
        Raises an appropriate exception if the request is not permitted.
        """
        for permission in self.get_permissions():
            if not permission.has_permission(request, self):
                self.permission_denied(
                    request, message=getattr(permission, 'message', None)
                )

这里的user方法属于 :

from rest_framework.request import Request
# Request类中--->user方法----->里面执行了  _authenticate(self) 方法

是 被伪装成数据属性,

@property
    def user(self):
        """
        Returns the user associated with the current request, as authenticated
        by the authentication classes provided to the request.
        """
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                # 这里的self 是 Request 对象
                self._authenticate()  # 这里执行的是 Request 类中的_authenticate()
        return self._user

重点的逻辑就是如下这段代码:

    # APIView-->dispatch--->self.initial--->self.perform_authentication(request)
    # --->reuqest.user---->self._authenticate
    def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        尝试依次使用每个身份验证实例对请求进行身份验证。
        """
        # 这里的 self.autenticators 是一个元组,里面存的是一个个实例化的对象
        # 这里元组 是 Request 类在实例化的时候执行 __init__方法的时候赋值来的
        for authenticator in self.authenticators:
            try:
                # 这里是执行自己写的认证类中的的 authenticate()方法,得到的是一个元组(这里面存的是 user对象  和 一个auth对象)!!!如果有返回值的话
                user_auth_tuple = authenticator.authenticate(self)
                # 如果该方法中返回 user对象 和 auth对象 
            except exceptions.APIException:
                self._not_authenticated()  # 如果没有通过认证走这个方法
                raise
           # 如果user_auth_tuple 有值,不为空
            if user_auth_tuple is not None:
                self._authenticator = authenticator
                self.user, self.auth = user_auth_tuple
                return

        self._not_authenticated()

认证组件的使用

models.py

from django.db import models


# Create your models here.


class Book(models.Model):
    title = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    publish_time = models.DateTimeField(auto_now_add=True)  # 自动添加创建时间
    authors = models.ManyToManyField('Author')
    publish = models.ForeignKey('Publish')  # 一对多
    
    def test(self):
        return self.title+'>>'+str(self.price)


class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    authordetail = models.OneToOneField('AuthorDetail')


class AuthorDetail(models.Model):
    tel_num = models.BigIntegerField()
    addr = models.CharField(max_length=32)


class Publish(models.Model):
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=32)
    email = models.EmailField()

# 用户类
class User(models.Model):
    name = models.CharField(max_length=32)
    pwd = models.CharField(max_length=32)
# token类 与用户类一对一关系
class Token(models.Model):
    user = models.OneToOneField(to='User')
    token = models.CharField(max_length=64)

认证类的建立(一般新建一个py文件)

from rest_framework.exceptions import AuthenticationFailed
from rest_framework.authentication import BaseAuthentication
from app01 import models

class MyAuth(BaseAuthentication):
    # 在认证组件中  需要重写 authenticate方法来 完成 用户认证逻辑
    def authenticate(self, request):
        # request值得是个对象
        token = request.GET.get('token')
        token_obj = models.Token.objects.filter(token=token).first()
        if token_obj:
            # 有值表示登录了
            return
        else:
            # 没有值,则报错
            raise AuthenticationFailed('您没有登录')

views.py

from django.shortcuts import render
from django.http.response import JsonResponse

# Create your views here.
from rest_framework.views import APIView
from rest_framework.response import Response
from app01 import models
from app01.myser import BookSerializer
from app01 import auths
from django.core.exceptions import ObjectDoesNotExist
import uuid




class Book(APIView):
    # 这里固定 需要些 这个认证类的列表,列表中存的是 一个一个认证的类
    authentication_classes = [auths.MyAuth,]
    # get 获取所有书籍信息
    def get(self, request, id):
        response = {'status': 100, 'msg': '成功'}
        print(id)
        if not id:
            book_list = models.Book.objects.all()
            # 第一个参数是要序列化的queryset对象,如果要序列化多条,必须制定many=True
            # 当instance形参被传入的实参是单个参数的时候,many=False
            book_serializer = BookSerializer(book_list, many=True)
        else:
            print(id)
            book_obj = models.Book.objects.filter(pk=id).first()
            book_serializer = BookSerializer(book_obj, many=False)
        print(book_serializer.data)
        response['books'] = book_serializer.data
        return Response(response)
    
    def post(self, request, id):
        response = {'status': 100, 'msg': '成功'}
        # 提交的字典
        book = request.data
        # 传统方法,创建对象保存
        print(book)
        
        # 新方法,通过序列化组件保存,必须继承自ModelSerializer
        book_ser = BookSerializer(data=book)
        # is_valid 提交的字段校验通过
        if book_ser.is_valid():
            book_ser.save()
            response['book'] = book_ser.data
        else:
            response['msg'] = book_ser.errors  # errors  是序列化类 中的钩子函数 raise来的报错信息
        return Response(response)
    
    def put(self, request, id):
        response = {'status': 100, 'msg': '修改成功'}
        if id:
            
            # 提交的字典
            book = request.data
            # 传统方法,创建对象保存
            print(book)
            book_obj = models.Book.objects.filter(pk=id).first()
            
            # 新方法,通过序列化组件保存,必须继承自ModelSerializer
            book_ser = BookSerializer(data=book, instance=book_obj)
            # is_valid 提交的字段校验通过
            if book_ser.is_valid():
                # 这里save()做修改
                book_ser.save()
                response['book'] = book_ser.data
            else:
                response['msg'] = book_ser.errors
        else:
            response['msg'] = '修改对象不存在'
        return Response(response)
    
    def delete(self, request, id):
        models.Book.objects.filter(pk=id).delete()
        response = {'status': 100, 'msg': '删除成功'}
        return Response(response)

class Login(APIView):
    # 局部禁用 认证
    authentication_classes = []
    def post(self, request):
        response = {'code': 100, 'msg': '登录成功'}
        name = request.data.get('name')
        pwd = request.data.get('pwd')
        print(name, pwd)
        try:
            # get()方法,获取 有且只有一条的 才不报错,其他情况都抛异常
            ret = models.User.objects.filter(name=name, pwd=pwd).get()
            
            # 登录成功后要去token 表中去存数据
            # 表里有 数据或没有数据
            # 1. 先生成随机字符串 用uuid
            token = uuid.uuid4()
            # 2. 存入token表
            # update_or_create()  方法  先查后改,查到就修改,没查到就新增  根据  user 去查
            models.Token.objects.update_or_create(user=ret, defaults={'token': token})
            response['token'] = token
        except ObjectDoesNotExist as exc:
            response['code'] = 101
            response['msg'] = '用户名或密码错误'
        except Exception as e:
            response['code'] = 102
            response['msg'] = str(e)
        return Response(response)

不存token的认证

def get_token(id,salt='123'):
    import hashlib
    md=hashlib.md5()
    md.update(bytes(str(id),encoding='utf-8'))
    md.update(bytes(salt,encoding='utf-8'))

    return md.hexdigest()+'|'+str(id)

def check_token(token,salt='123'):
    ll=token.split('|')
    import hashlib
    md=hashlib.md5()
    md.update(bytes(ll[-1],encoding='utf-8'))
    md.update(bytes(salt,encoding='utf-8'))
    if ll[0]==md.hexdigest():
        return True
    else:
        return False

class TokenAuth():
    def authenticate(self, request):
        token = request.GET.get('token')
        success=check_token(token)
        if success:
            return
        else:
            raise AuthenticationFailed('认证失败')
    def authenticate_header(self,request):
        pass
class Login(APIView):
    def post(self,reuquest):
        back_msg={'status':1001,'msg':None}
        try:
            name=reuquest.data.get('name')
            pwd=reuquest.data.get('pwd')
            user=models.User.objects.filter(username=name,password=pwd).first()
            if user:
                token=get_token(user.pk)
                # models.UserToken.objects.update_or_create(user=user,defaults={'token':token})
                back_msg['status']='1000'
                back_msg['msg']='登录成功'
                back_msg['token']=token
            else:
                back_msg['msg'] = '用户名或密码错误'
        except Exception as e:
            back_msg['msg']=str(e)
        return Response(back_msg)
from rest_framework.authentication import BaseAuthentication
class TokenAuth():
    def authenticate(self, request):
        token = request.GET.get('token')
        token_obj = models.UserToken.objects.filter(token=token).first()
        if token_obj:
            return
        else:
            raise AuthenticationFailed('认证失败')
    def authenticate_header(self,request):
        pass

class Course(APIView):
    authentication_classes = [TokenAuth, ]

    def get(self, request):
        return HttpResponse('get')

    def post(self, request):
        return HttpResponse('post')

总结:

  • 局部使用: 只要在 需要认证的类里面 固定写入你所需的哪些认证(认证类)

     # 这里固定 需要些 这个认证类的列表,列表中存的是 一个一个认证的类
        authentication_classes = [auths.MyAuth,]
  • 全局使用: 只要在settings文件中配置 REST_FRAMEWORK 即可

    # settings.py 中配置
    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES": ["app01.auths.MyAuth", ]
    }
  • 局部禁用:只要在不需要使用全局使用的认证的 视图类中清除authentication_classes中你不需要的认证即可

    authentication_classes=[]

阅读源码体会:

  • 如果在settings.py 文件中配置了 REST_FRAMEWORK,先默认从项目settings中取
  • 如果取不到,才去默认的drf配置文件中取
  • 用户如果在 视图类中配置了authentication_classes, 则优先从用户配置的取

ps:先取视图类中的配置的认证--->再取 项目settings文件中配置的----->最后取默认配置