内容概览
- 权限类使用
- 频率类使用
- 认证源码分析
- 权限源码分析
- 频率源码简单分析
- 鸭子类型
权限类使用
"""
第一步:写一个类继承BasePermission
第二步:重写BasePermission中的has_permission方法
第三步:在方法中校验用户是否有权限(用户认证后request.user就是当前用户)
第四步:如果有权限返回True,否则返回False
第五步:使用self.message自定义给前端返回的信息
第六步:全局使用/局部使用/局部禁用
"""
from rest_framework.permissions import BasePermission
"""写一个类,继承BasePermission"""
class BookPermission(BasePermission):
message = '返回给前端的提示信息'
"""重写has_permission方法"""
def has_permission(self, request, view):
"""校验用户权限,通过返回True,否则返回False"""
user_type = request.user.user_type
# self.message = f'当前为{request.user.get_user_type_display()},暂无权限' # 使用choice字段属性后,直接获取字段拿到的是数字类型,想要拿到字符串需要使用get_字段名_display()
return True if user_type == 1 else False
在setting中配置全局生效
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ['app01.permissions.BookPermission']
}
在视图类中配置局部生效或局部禁用
class UserView(ViewSet):
permission_classes = [BookPermission,] # 局部生效
permission_classes = [] # 局部禁用
频率类使用
"""
第一步:写一个类继承SimpleRateThrottle
第二步:重写get_cache_key方法,返回唯一的字符串(用户id/ip地址),会以这个字符串作为频率限制的依据
第三步:写一个类属性scope = 'xxx',值可以任意字符,需要与配置文件对应
第四步:编写配置文件
"""
from rest_framework.throttling import SimpleRateThrottle
class Throttle_Class(SimpleRateThrottle):
scope = 'scope'
def get_cache_key(self, request, view):
return request.META.get('REMOTE_ADDR')
配置文件:
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'scope':'3/m' # 与类属性对应,参数为多长时间能够访问多少次;s/秒,m/分,h/小时,d/天
},
'DEFAULT_THROTTLE_CLASSES': ['app01.throttle.Throttle_Class'],
} # 全局配置
视图函数:
class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):
throttle_classes = [Throttle_Class,] # 局部配置
throttle_classes = [] # 局部禁用
认证源码分析
# 写个认证类,重写某个方法,配置在视图类上,就有认证了---》认证类加了,在视图类的方法中,request.user就是当前登录用户---》猜认证类的执行,是在在视图类的方法之前执行的
# 源码分析:
-之前咱们读APIView的执行流程---》包装了新的request,执行了3大认证,执行视图类的方法,处理了全局异常
-入口:APIView的dispatch
-APIView的dispatch的496行上下:self.initial(request, *args, **kwargs)
-APIView的initial
-413行上下:有三句话,分别是:认证,权限,频率
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
-读认证类的源码---》APIView的perform_authentication(request),315行上下
def perform_authentication(self, request):
request.user # 新的request
-request是新的request---》Request类中找user属性(方法),是个方法包装成了数据属性
-来到Request类中找:220行
def user(self):
if not hasattr(self, '_user'): # Request类的对象中反射_user
with wrap_attributeerrors():
self._authenticate() # 第一次会走这个代码
return self._user
-Request的self._authenticate()---》373行
def _authenticate(self):
for authenticator in self.authenticators: # 配置在视图类中所有的认证类的对象,在实例化Request对象时通过推导式获取
"""
def get_authenticators(self):
return [auth() for auth in self.authentication_classes]
"""
try:
#(user_token.user, token)
user_auth_tuple = authenticator.authenticate(self) # 调用认证类对象的authenticate
except exceptions.APIException:
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator # 忽略
self.user, self.auth = user_auth_tuple # 解压赋值
return
# 认证类可以配置多个,但是如果有一个返回了两个值,后续的就不执行了
self._not_authenticated()
# 总结:认证类,要重写authenticate方法,认证通过返回两个值或None,认证不通过抛AuthenticationFailed(继承了APIException)异常
权限源码分析
APIView的check_permissions(request),325行上下
def check_permissions(self, request):
for permission in self.get_permissions():
# permission是咱们配置在视图类中权限类的对象,对象调用它的绑定方法has_permission
# 对象调用自己的绑定方法会把自己传入(权限类的对象,request,视图类的对象)
if not permission.has_permission(request, self): # 如果返回值为False执行if内代码,返回True则直接跳过
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
) # 内部实际上也是使用raise抛错
-APIVIew的self.get_permissions(),273行上下
return [permission() for permission in self.permission_classes]
-self.permission_classes 就是咱们在视图类中配的权限类的列表
-所以这个get_permissions返回的是 咱们在视图类中配的权限类的对象列表[UserTypePermession(),]
频率源码简单分析
# 源码分析:
-之前咱们读APIView的执行流程---》包装了新的request,执行了3大认证,执行视图类的方法,处理了全局异常
-入口:APIView的dispatch
-APIView的dispatch的496行上下:self.initial(request, *args, **kwargs)
-APIView的initial
-413行上下:有三句话,分别是:认证,权限,频率
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
-APIView的check_throttles:351上下
def check_throttles(self, request):
throttle_durations = []
for throttle in self.get_throttles():
if not throttle.allow_request(request, self):
throttle_durations.append(throttle.wait())
-总结:要写频率类,必须重写allow_request方法,返回True(没有到频率的限制)或False(到了频率的限制)
鸭子类型
假设有A和B两个类,B不需要继承A这个类,只要B中有A的属性和方法,那么它就是A这个类
但如果方法写错了,那么就不是A这个类了,python解决这个问题有两种方法
- abc模块,装饰后必须重写被装饰的方法,不然就报错
- drf源码中使用的,父类中的这个方法直接抛异常
练习
# 1 编写图书和出版社的5个接口,所有接口都要有一分钟访问5次的频率限制
# 2 图书的接口需要登录才能方法,出版社的接口需要登录,并且是超级用户才能访问
# 3 继承BaseThrottle编写频率类
# 自定义的逻辑 {ip:[时间1,时间2]}
#(1)取出访问者ip
#(2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
#(3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
#(4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
#(5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
models.py
from django.db import models
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
user_type = models.IntegerField(choices=((1, '超极管理员'), (2, '会员用户'), (3, '普通用户')), default=3)
def __str__(self):
return self.username
class UserToken(models.Model):
user = models.OneToOneField(to='User', on_delete=models.CASCADE)
token = models.CharField(max_length=64, null=True)
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.DecimalField(max_digits=5, decimal_places=2)
# publish_date = models.DateField(null=True)
publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)
def publish_dict(self):
return {'name':self.publish.name, 'city': self.publish.city, 'email': self.publish.email}
def __str__(self):
return self.name
class Publish(models.Model):
name = models.CharField(max_length=32)
city = models.CharField(max_length=32)
email = models.EmailField()
def __str__(self):
return self.name
urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework.routers import SimpleRouter
from app01 import views
router = SimpleRouter()
router.register('user', views.UserView, 'user')
router.register('books', views.BookView, 'books')
router.register('publish', views.PublishView, 'publish')
urlpatterns = [
path('admin/', admin.site.urls),
path('', include(router.urls)),
]
serializers
from rest_framework.serializers import ModelSerializer
from app01 import models
class BookModelSerializer(ModelSerializer):
class Meta:
model = models.Book
fields = ['id', 'name', 'price', 'publish', 'publish_dict']
extra_kwargs = {
'publish': {'write_only': True},
'publish_dict': {'read_only': True},
}
class PublishModelSerializer(ModelSerializer):
class Meta:
model = models.Publish
fields = '__all__'
views.py
import uuid
from django.shortcuts import render
from django.http import JsonResponse
from rest_framework.viewsets import ModelViewSet, ViewSet
from rest_framework.decorators import action
from app01 import models
from app01 import serializers
# Create your views here.
class UserView(ViewSet):
authentication_classes = []
permission_classes = []
@action(['POST', ], detail=False, url_path='login', url_name='login')
def login(self, request):
username = request.POST.get('username')
password = request.POST.get('password')
user_obj = models.User.objects.filter(username=username, password=password).first()
if user_obj:
token = str(uuid.uuid4())
models.UserToken.objects.update_or_create({'token': token}, user=user_obj)
return JsonResponse({'code': 100, 'msg': '登录成功'})
else:
return JsonResponse({'code': 101, 'msg': '用户名或密码错误'})
class BookView(ModelViewSet):
queryset = models.Book.objects.all()
serializer_class = serializers.BookModelSerializer
permission_classes = []
class PublishView(ModelViewSet):
queryset = models.Publish.objects.all()
serializer_class = serializers.PublishModelSerializer
三大认证
"""authentication"""
from rest_framework.authentication import BaseAuthentication
from app01 import models
from rest_framework.exceptions import AuthenticationFailed
class LoginAuth(BaseAuthentication):
def authenticate(self, request):
token = request.META.get('HTTP_TOKEN')
user_token_obj = models.UserToken.objects.filter(token=token).first()
if user_token_obj:
return user_token_obj.user, token
else:
raise AuthenticationFailed('当前没有登录')
"""permissions"""
from rest_framework.permissions import BasePermission
class App01_Permission(BasePermission):
def has_permission(self, request, view):
pm = request.user.user_type
if pm == 1:
return True
else:
self.message = f'当前为{request.user.get_user_type_display()},权限不足'
return False
"""throttles"""
import datetime
from rest_framework.throttling import SimpleRateThrottle, BaseThrottle
class Throttle_app01(BaseThrottle):
scope = 'scope'
user_throttle = {}
def allow_request(self, request, view):
print(self.user_throttle)
user_ip = request.META.get('REMOTE_ADDR')
if user_ip not in self.user_throttle:
self.user_throttle[user_ip] = [datetime.datetime.now()]
return True
else:
self.user_throttle[user_ip].insert(0, datetime.datetime.now())
time_delta = datetime.timedelta(minutes=1)
time_list = self.user_throttle[user_ip]
for i in time_list:
if i < datetime.datetime.now() - time_delta:
self.user_throttle.get(user_ip).pop()
if len(self.user_throttle.get(user_ip)) > 5:
return False
return True
"""setttings"""
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ['app01.authenticates.LoginAuth'],
'DEFAULT_PERMISSION_CLASSES': ['app01.permissions.App01_Permission'],
# 'DEFAULT_THROTTLE_RATES': {
# 'scope': '5/m'
# },
'DEFAULT_THROTTLE_CLASSES': ['app01.throttles.Throttle_app01'],
}