drf07 认证、权限、频率


drf07 认证、权限、频率

今日内容

  • 认证
  • 权限
  • 频率
  • 权限源码分析

1、认证Authentication

# 作用:校验用户是否登录,如果登录了,继续往后走,如果没有登录,直接返回

# 登录功能---》


# 认证的使用
	-第一步:写一个认证类,继承BaseAuthentication,重写authenticate  方法
    -第二步:在 authenticate 方法中判断用户是否登录(取出用户携带的token,去判断)
    -第三步:如果认证通过,返回两个值,如果认证不通过抛异常
    	-# 在后续的request对象中,有这两个值,第一个给了request.user,第二个值给了request.auth
    -第四步:把写的认证类,配置在视图类中(跟请求和响应的配置一样),全局配置
    
    
    
# 写一个认证类,继承BaseAuthentication
	# 鸭子类型:不显示的继承某个类,只要类中有共同的属性和方法,我们就属于同一类
    通俗的讲:
    	比如有两个类,第一个类中有post方法和别的属性,第二个类有和第一个类一模一样的属性和方法,这样我们就属于同一类。
        第二个类有第一个类一样的属性和方法,这样就是鸭子类型。

1.1 前提准备

1.1.1 models.py

from django.db import models


# Create your models here.

class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.IntegerField()
    type = models.IntegerField(choices=((0, '普通用户'), (1, '普通管理员'), (2, '超级管理员')), null=True)

    def __str__(self):
        return self.username


class UserToken(models.Model):
    token = models.CharField(max_length=32)
    user = models.OneToOneField(to='User', on_delete=models.CASCADE)


class Book(models.Model):
    title = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    publish = models.CharField(max_length=32)


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


class Author(models.Model):
    name = models.CharField(max_length=32)
    sex = models.IntegerField(choices=((1, '男'), (2, '女')))


class AuthorDetail(models.Model):
    phone = models.IntegerField()
    addr = models.CharField(max_length=32)

1.1.2 自定义序列化类

from rest_framework import serializers
from app01.models import User, UserToken, Book, Publish, Author


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = '__all__'


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'


class PublsihSerializer(serializers.ModelSerializer):
    class Meta:
        model = Publish
        fields = '__all__'


class AuthorSerializer(serializers.ModelSerializer):
    class Meta:
        model = Author
        fields = '__all__'

1.1.3 views.py

from django.shortcuts import render
from rest_framework.response import Response
from app01.models import User, UserToken
from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
import uuid  # 生成一个不重复的字符串


# Create your views here.

# 登录接口
class UserView(ViewSet):
    @action(methods=['post'], detail=False)
    def login(self, request):
        # post请求都在request.data中
        username = request.data.get('username')
        password = request.data.get('password')
        user_obj = User.objects.filter(username=username, password=password).first()
        if user_obj:
            # 登录成功返回:{code:200, msg:登录成功, token:'fdf'}
            token = str(uuid.uuid4())  # 转为字符串
            # 存入库中,因为外键关系是一对一,存入一条数据,在存入会报错。使用update_or_create方法,如果有数据就更新,没有就创建
            # 通过user=user_obj去查,如果查到了,就使用defaults更新 ,如果查不到,使用所有新增
            UserToken.objects.update_or_create(defaults={'token': token}, user=user_obj)
            return Response({'code': 200, 'msg': '登录成功', 'token': token})
        else:
            return Response({'code': 201, 'msg': '用户名或密码错误'})


# 快速写5个接口
from app01.serializer import BookSerializer
from rest_framework.viewsets import ModelViewSet
from app01.models import Book
from app01.auth import LoginAuth  # 自己编写的认证类


# 5个接口都需要登录以后才能访问
class BookView(ModelViewSet):
    authentication_classes = [LoginAuth]
    serializer_class = BookSerializer
    queryset = Book.objects.all()
    
from app01.auth import CommonAdminPermission, SuperAdminPermission
# 普通管理员操作publish
class PublishView(ModelViewSet):
    permission_classes = [CommonAdminPermission]
    serializer_class = PublsihSerializer
    queryset = Publish.objects.all()


# 加上频率类
from app01.auth import MyThrottling
class AuthorView(ModelViewSet):
    # permission_classes = [SuperAdminPermission]
    throttle_classes = [MyThrottling]
    serializer_class = AuthorSerializer
    queryset = Author.objects.all()

1.2 认证基本使用

1.2.1 第一步:写一个认证类auth.py

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


class LoginAuth(BaseAuthentication):
    # 重写authenticate(方法):源码中是,验证请求并返回两个元祖(用户、令牌)
    def authenticate(self, request):
        token = request.query_params.get('token')  # get地址中求取
        # 请求头中取,用request.META:就是http请求头中的数据,自动加上HTTP_大写
        token = request.META.get('HTTP_TOKEN')
        # 去数据库查询是否存在,,如果存在取出对应的用户,也就是当前登录用户
        user_token = UserToken.objects.filter(token=token).first()
        if user_token:
            # 登录就返回两个元祖:(用户、令牌)
            return user_token.user, token  # 在后续的request对象中,有这两个值,第一个给了request.user,第二个值给了request.auth
        else:
            raise AuthenticationFailed('未登录')
            
'''
第一步:自定义一个认证类并且继承BaseAuthentication,还有从写authenticate方法
第二步:在authenticate方法中判断用户是否的登录,根据用户携带的token,去判断
第三步:如果认证通过,返回两个值,如果认证不通过抛异常
	-# 在后续的request对象中,有这两个值,第一个给了request.user,第二个值给了request.auth
	
'''

1.2.2 第四步:局部配置认证类

# 第四步:把写的认证类,配置在视图类中(跟请求和响应的配置一样),全局配置

# 快速写5个接口
from app01.serializer import BookSerializer
from rest_framework.viewsets import ModelViewSet
from app01.models import Book
from app01.auth import LoginAuth  # 自己编写的认证类


# 5个接口都需要登录以后才能访问
class BookView(ModelViewSet):
    authentication_classes = [LoginAuth]  # 局部配置认证类
    serializer_class = BookSerializer
    queryset = Book.objects.all()

1.2.3全局配置认证类

# settion.py
REST_FRAMEWORK = {
    # 全局使用认证类,所有的接口都要登录才能用,但是登录接口也被配上了,所以要取消登录接口的
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'app01.auth.LoginAuth'
    ]
}

# 取消登录接口的认证
class UserView(ViewSet):
    authentication_classes = []  # 在登录接口中设置为空,没有任何认证
    '''因为优先用自己的配置,在用配置文件'''

2、权限Permissions

# 权限控制可以限制用户对于视图的访问和对于具体数据对象的访问。

# 思路
	1.根据认证类返回的当前用户type属性,设置权限

# 作用
    1.普通用户,普通管理员,超级管理员
    2.普通用户登录,可以操作book和publish的所有接口,不能操作其他的
    3.普通管理员的登录,可以操作 book和publish的所有接口,不能操作其他
    4.超级管理员,可以操作所有接口
    
# 权限的使用
    -第一步:写一个权限类,继承BasePermission,重写has_permission  方法
    -第二步:在 重写has_permission 方法中判断用户是否有权限(request.user.user_type)
    -第三步:如果有权限,返回True,如果没有返回False
    -第四步:把写的权限类,配置在视图类中(跟请求和响应的配置一样),全局配置

2.1 创建权限类auth.py

# 普通管理员的权限类
from rest_framework.permissions import BasePermission
class CommonAdminPermission(BasePermission):
    def has_permission(self, request, view):
        # 1.判断用户的权限
        # request.user :当前登录用户,因为权限类在认证类后执行,所以一旦认证类通过,request.user就是当前登录用户
        if request.user.type in [1,2]:
            return True
        else:
            return False
        
        
# 超级管理员的权限类
class SuperAdminPermission(BasePermission):
    def has_permission(self, request, view):
        # 1.判断用户的权限
        # request.user :当前登录用户,因为权限类在认证类后执行,所以一旦认证类通过,request.user就是当前登录用户
        if request.user.type == 2:
            return True
        else:
            return False
        
'''
总结:
	因为先执行认证类再执行权限类,认证类后会返回当前登录用户,可以根据当前用户的type属性做判断,
	来设置权限。
'''

2.1.1 局部配置权限类

from app01.auth import CommonAdminPermission,SuperAdminPermission
# 普通管理员操作publish
class PublishView(ModelViewSet):
    permission_classes = [CommonAdminPermission]  # 配置普通管理员权限
    serializer_class = PublsihSerializer
    queryset = Publish.objects.all()
    '''只有权限是普通管理员或者超级管理员 才能操作接口'''
    
# 超级管理员操作author
class AuthorView(ModelViewSet):
    permission_classes = [SuperAdminPermission]  # 配置超级管理员权限
    serializer_class = AuthorSerializer
    queryset = Author.objects.all()
    '''只有权限是超级管理员 才能操作接口'''

2.1.2 全局配置权限类

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        # 全局使用权限类,所有的接口都要是超级管理员才能访问
        'app01.auth.SuperAdminPermission'
    ],
}

3、频率

# 限制访问次数

# 思路
	1.根据用户ip或用户id限制访问次数
    
# 频率的使用
	-第一步:写一个类,继承SimpleRateThrottle,重写get_cache_key
    -第二步:get_cache_key返回什么就以什么做限制,必须写类属性 scope='字符串'
    -第三步:配置文件中配置
      'DEFAULT_THROTTLE_RATES': {
        '字符串': '3/m',  # key:ip_1m_3 对应频率类的scope属性, value: 3/m 一分钟访问3次
          # m:分钟
          # s:秒
          # h:小时
          # d:天
    	},
            
     -第四步:局部使用和全局使用
    
# 限制一个ip一分钟只能访问3次

3.1 创建频率类

# 频率限制类:一分钟只能访问3次
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle


class MyThrottling(SimpleRateThrottle):
    scope = 'ip_1m_3'  # 2.scope必须写

    # 1.重写get_cache_key方法
    def get_cache_key(self, request, view):
        # 返回什么就以什么作为限制(ip地址)
        # 以用户id作为限制,前提必须登录request.user取出值
        # return request.user.pk

        # 使用客户端地址做为限制
        return request.META.get('REMOTE_ADDR')
    
3.在配置文件配置
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_RATES': {
        # 设置一分钟访问3次
        'ip_1m_3': '3/m',  # key:ip_1m_3 对应频率类的scope属性, value: 3/m 一分钟访问3次
    },
}

3.1.1 局部配置频率类

from app01.auth import MyThrottling
class AuthorView(ModelViewSet):
    throttle_classes = [MyThrottling]  # 局部配置频率类
    serializer_class = AuthorSerializer
    queryset = Author.objects.all()

3.1.2 全局配置频率类

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_RATES': {
        # 配置一分钟访问3次
        'ip_1m_3': '3/m',  # key:ip_1m_3 对应频率类的scope属性, value: 3/m 一分钟访问3次
    },
    # 全局配置,一分钟只能访问三次
    'DEFAULT_THROTTLE_CLASSES': [
        'app01.auth.MyThrottling'
    ],
}

4、权限源码分析

# 1.APIView里面重写了dispatch方法
    def dispatch(self, request, *args, **kwargs):
        try:
            # 并且里面有三大认证:认证、权限,频率
            self.initial(request, *args, **kwargs)
            
2.在initial里面写了三大认证
     def initial(self, request, *args, **kwargs):
            self.perform_authentication(request)  # 先走认证
            self.check_permissions(request)       # 再走权限
            self.check_throttles(request)         # 最后再走频率
            
3.进入check_permissions方法
    def check_permissions(self, request):
        # self.get_permissions()是:视图类中的permission_classes = [SuperAdminPermission]
        for permission in self.get_permissions():
            # 每次从列表取出一个对象(配置在视图函数中限制类的对象),执行对象的has_permission方法
            # 其中self是视图类的对象,也就是执行视图类自己定义权限类的has_permission方法
            if not permission.has_permission(request, self):  # 认证失败才往下走
                self.permission_denied(
                    request,
                    message=getattr(permission, 'message', None),
                    code=getattr(permission, 'code', None)
                )
                
4.进入self.get_permissions():
        def get_permissions(self):
        """
        实例化并返回此视图所需的权限列表。
        """
        # 每次运行都会自动加括号,视图类permission_classes = [SuperAdminPermission()]
        return [permission() for permission in self.permission_classes]
drf