认证、权限、频率
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]
5、认证源码分析
# 1.APIView里面重写了dispatch方法
class APIView:
def dispatch(self, request, *args, **kwargs):
try:
# 并且里面有三大认证:认证、权限,频率
self.initial(request, *args, **kwargs)
2.在initial里面写了三大认证
class APIView:
def initial(self, request, *args, **kwargs):
self.perform_authentication(request) # 先走认证
self.check_permissions(request) # 再走权限
self.check_throttles(request) # 最后再走频率
3.进入perform_authentication方法,需要去drf的request对象中找user属性(方法)
class APIView:
def perform_authentication(self, request):
# 需要去drf的request对象中找user属性(方法)
request.user
4.进入Request的user,刚开始还没用户,所以走self._authenticate()
from rest_framework.request import Request
@property
def user(self):
if not hasattr(self, '_user'):
# 没用户,打开上下文管理器
with wrap_attributeerrors():
# 没用户,认证出用户
self._authenticate() # 走的这一步
# 有用户,直接返回用户
return self._user
5.核心就是Request类中的_authenticate
class Request:
def _authenticate(self):
# 遍历拿到一个个认证器,进行认证
# 所以说每次循环,拿到一个视图类中配置authentication_classes=[类名]对象
for authenticate in self.authenticators: # self:Request,去Request找authenticators属性
try:
# 认证器(对象)调用认证方法authenticate(认证类对象self.request请求对象)
# 返回值:登录的用户和认证的信息组成的tuple
# 该方法被try包裹,代表该方法抛异常,抛异常代表认证失败
user_auth_tuple = authenticator.authenticate(self) # 是Request对象
except exceptions.APIException:
self._not_authenticated()
raise
# 返回值处理
if user_auth_tuple is not None:
self._authenticator = authenticator
# 解压赋值:如果有返回值,就将就将登录用户和登录认证分别保存到request分别保存到request.user、request.auth
self.user, self.auth = user_auth_tuple
return
# 如果返回值user_auth_tuple为空,代表认证通过但是没有登录用户和登录认证信息,代表是游客
self._not_authenticated()
Request类中self.authenticators又是从哪里传过来的值?
1.先是在APIView中进入到drf中request方法
class APIView:
def dispatch(self, request, *args, **kwargs):
# 封装新的request方法,self中的是django中request
request = self.initialize_request(request, *args, **kwargs)
# drf中request方法
def initialize_request(self, request, *args, **kwargs):
return Request(
request,
parsers=self.get_parsers(),
# 而authenticators就是这传进来的Request
authenticators=self.get_authenticators(), # 列表[类的对象]
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
def get_authenticators(self):
# 列表中是一堆对象,视图类中配置authentication_classes=[类名]对象
return [auth() for auth in self.authentication_classes] # 配置认证类每次会实例化得到一个对象
2.又把authenticators返回给了Request类
class Request:
# authenticators是APIView中dispatch方法传进来的值
def __init__(self, request, parsers=None, authenticators=None,
# 又把值赋值给了self.authenticators
self.authenticators = authenticators or () # 这里又被Request类中的_authenticate循环
6、频率源码分析
1、在进入源码里
class MyThrottling(SimpleRateThrottle): # 这里进入
scope = 'ip_1m_3'
2、利用反射机制找属性
class SimpleRateThrottle(BaseThrottle):
cache = default_cache # 缓存
timer = time.time # 当前时间
cache_format = 'throttle_%(scope)s_%(ident)s'
scope = None # 配置中key
THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES # 配置信息
def __init__(self):
if not getattr(self, 'rate', None):
# 调用了get_rate方法
self.rate = self.get_rate() # rate:3/m
# 将频率配置解析成次数和时间,分别 存放到self.num_requests、self.duration
self.num_requests, self.duration = self.parse_rate(self.rate) # 调用了parse_rate方法
3、进入get_rate方法
def get_rate(self):
if not getattr(self, 'scope', None):
msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
self.__class__.__name__)
raise ImproperlyConfigured(msg)
try:
# 根据key取出values值,把values值返回__init__
return self.THROTTLE_RATES[self.scope] # scope就是配置文件中的"scope"
except KeyError:
msg = "No default throttle rate set for '%s' scope" % self.scope
raise ImproperlyConfigured(msg)
4、进入parse_rate方法
def parse_rate(self, rate):
if rate is None:
return (None, None)
# 通过/把"3/m"转成了3 m,并且解压赋值
num, period = rate.split('/')
# 把3转为整形赋值给num_requests
num_requests = int(num)
# 根据字段0的索引取值,m对应'm': 60
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
# 把3和60返回到__init__
return (num_requests, duration)
5、走完上面几步,证明了有值,往下面走
def allow_request(self, request, view):
if self.rate is None: # 如果没有值返回True
return True
# 当前登录用户的IP地址
self.key = self.get_cache_key(request, view) # 返回的是ip地址
# 判断是否有值,如果返回的是None,就不做限制
if self.key is None:
return True
# 初次访问缓存为空,self.history为[],是存放时间的列表
self.history = self.cache.get(self.key, []) # cache.get:获取缓存
# 获取当前的时间,存放到self.now
self.now = self.timer()
# 判断self.history是否有值,并且当前时间与第一次访问时间间隔如果大于60s,第一次记录清除,不算作一次计数
# self.now:10:56 比如
# self.duration:[10:23,10:55]
while self.history and self.history[-1] <= self.now - self.duration:
self.history.pop() # 有值,删除缓存
# 用history和 限制次数3进行比较
# history长度第一次访问是0,第二次访问是1,第三次访问是2,直到第四次访问是3为失败
if len(self.history) >= self.num_requests: # self.num_requests:是解压赋值中的3
# 直接返回False,代表频率限制
return self.throttle_failure()
# 如果history的长度未达到限制次数,代表可以访问
# 将当前时间插入到history列表的开头,将history列表作为数据存到缓存中
return self.throttle_success() # 调用了throttle_success方法
6、进入了throttle_success方法
def throttle_success(self):
# 把将当前时间插入到history列表的开头
self.history.insert(0, self.now)
# 将history列表作为数据存到缓存中,key就是'ip_1m_3': '3/m' 中的ip_1m_3
# self.duration就是超时时间60s就会删除
self.cache.set(self.key, self.history, self.duration) # duration:就是__init__解析的60
return True
7、最后计算下一次访问需要等待的时间
def wait(self):
if self.history:
remaining_duration = self.duration - (self.now - self.history[-1])
else:
remaining_duration = self.duration
available_requests = self.num_requests - len(self.history) + 1
if available_requests <= 0:
return None
return remaining_duration / float(available_requests)

浙公网安备 33010602011771号