DRF 认证、权限、频率
1. 认证类使用
1.1 使用步骤
# 导入:
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
# 1.写一个认证类继承BaseAuthentication
class LoginAuth(BaseAuthentication):
pass
# 2. 重写父类中的authenticate方法
class LoginAuth(BaseAuthentication):
def authenticate(self, request):
pass
# 3. 在内部判断用户是否登录
--认证条件的数据在哪里?自己规定,可以在请求体,或请求头,路由后。
--http请求头的数据从request.META中取,统一取:HTTP_请求头大写
token = request.META.get('HTTP_TOKEN')
--请求体在 request.data
token =request.data.get('token')
--路由后在 request.GET中取:
token = request.GET.get('HTTP_TOKEN')
--通过token判断用户是否登录:表中查询。
user_token_obj = UserToken.objects.filter(token=token).first()
--如果登录返回俩个值,一个是当前登录用户,一个是token
if user_token_obj:
return user_token_obj.user, token
--如果没登录,直接抛AuthenticationFailed
else:
raise AuthenticationFailed('你当前暂未登录')
# 4. 认证类写好后,使用
--局部使用在需要被认证的类中添加
class BookView(ModelViewSet):
authentication_classes = [认证类名,]
# 局部使用仅在当前需要认证的类中生效
--全局使用在settings.py中配置
REST_FRAMEWORK={
'DEFAULT_AUTHENTICATION_CLASSES':['app01.auth.认证类名称',]
}
# 全局使用在所写的所有类中都生效,个别不需要认证需要加入局部禁用
--局部禁用在需要禁用的类中添加
class BookView(ModelViewSet):
authentication_classes = []
1.2 认证类代码
# 新建一个py文件,auth.py 写认证类 LoginAuth
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from .models import UserToken
# 写一个认证类并且继承BaseAuthentication
class LoginAuth(BaseAuthentication):
# 重写父类BaseAuthentication中的authenticate方法
def authenticate(self, request):
# 取出token进行校验判断是否登录
token = request.META.get('HTTP_TOKEN')
user_token_obj = UserToken.objects.filter(token=token).first()
if user_token_obj:
# 如果登录返回俩个值,一个是当前登录用户,一个是token
return user_token_obj.user, token
else:
# 如果没登录,直接抛AuthenticationFailed
raise AuthenticationFailed('你当前暂未登录')
# 在view.py中使用
--局部使用
class BookView(ModelViewSet):
# 局部使用:authentication_classes = [LoginAuth,]
authentication_classes = [LoginAuth,]
queryset = Book.objects.all()
serializer_class = BookSerializer
--全局使用
settings.py中配置
REST_FRAMEWORK={
'DEFAULT_AUTHENTICATION_CLASSES':['app01.auth.认证类名称',]
}
--局部禁用
在全局配置使用中可能有的不需要认证,需要使用局部禁用
class BookView(ModelViewSet):
authentication_classes = []
1.3 认证源码分析
# 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: # 配置在视图类中所有的认证类的对象
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)异常
2.权限类使用
2.1 使用步骤
# 导入
from rest_framework.permissions import BasePermission
# 1. 写一个权限类继承BasePermission
class UserTypePermission(BasePermission):
pass
# 2. 重写父类BasePermission中的has_permission方法
class UserTypePermission(BasePermission):
def has_permission(self, request, view):
# 3. 在方法内部校验用户是否有权限,如果有权限返回True
if request.user.user_type == 1:
return True
# 4. 没有则返回False,可以设置self.message 是给前端的提示信息
else:
self.message = "你是2B,没有权限访问"
return False
# 5. 在view.py中使用
--局部使用
class PublishView(ModelViewSet):
permission_classes = [UserTypePermission, ] # 权限校验
queryset = Publish.objects.all()
serializer_class = PublishSerializer
--全局使用
# 在settings.py中添加配置
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ['app01.permission.UserTypePermission',]
}
--局部禁用
在全局配置使用中可能有的不需要使用权限校验,需要使用局部禁用
class PublishView(ModelViewSet):
permission_classes = []
queryset = Publish.objects.all()
serializer_class = PublishSerializer
2.2 权限类代码
# 新建一个py文件,permission.py 写权限类 UserTypePermission
permission.py
# 写一个权限类继承BasePermission
class UserTypePermission(BasePermission):
# 也可以直接在这里设置前端提示 message = "你是2B,没有权限访问"
# 重写has_permission方法
def has_permission(self, request, view):
# 在方法内部校验用户是否有权限
if request.user.user_type == 1:
# 如果有权限,返回True
return True
else:
# 可以设置self.message 是给前端的提示信息
self.message = "你是2B,没有权限访问"
# 没有则返回False
return False
view.py
# 局部使用
class PublishView(ModelViewSet):
permission_classes = [UserTypePermission, ] # 权限校验
queryset = Publish.objects.all()
serializer_class = PublishSerializer
# 全局使用,在settings.py中添加配置
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES':['app01.permission.UserTypePermission',]}
# 局部禁用,在全局配置使用中可能有的不需要使用权限校验,需要使用局部禁用
class PublishView(ModelViewSet):
permission_classes = []
queryset = Publish.objects.all()
serializer_class = PublishSerializer
2.3 权限类源码分析
-先读最简单的权限执行流程---》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):
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
-APIVIew的self.get_permissions(),273行上下
return [permission() for permission in self.permission_classes]
-self.permission_classes 就是咱们在视图类中配的权限类的列表
-所以这个get_permissions返回的是 咱们在视图类中配的权限类的对象列表[UserTypePermession(),]
# 总结:权限类源码
-为什么要写一个类,重写has_permission方法,有三个参数,为什么一定要return True或False,messgage可以做什么用
3.频率类使用
3.1 使用步骤
# 导入
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
# 1. 写一个类继承SimpleRateThrottle
class Throttle(SimpleRateThrottle):
pass
# 2. 写一个类属性,scope="xxx"
class Throttle(SimpleRateThrottle):
scope = 'xxx' # xxx可以是任意名字。
pass
# 3. 重写SimpleRateThrottle中的def get_cache_key方法
class Throttle(SimpleRateThrottle):
scope = 'xxx'
def get_cache_key(self, request, view):
# 返回唯一的字符串,会以这个字符串做频率限制
return request.META.get('REMOTE_ADDR')
# 4. 配置文件中写
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'xxx': '5/m'}}
xxx:是频率类中scope中的那个字母
5/m: 表示每分钟可以访问5次,5为次数,m代表时间
s:每秒、h:每小时、d:每天
# 5. 使用
--局部配置
class PublishView(ModelViewSet):
throttle_classes = [Throttle,]
queryset = Publish.objects.all()
serializer_class = PublishSerializer
--全局配置
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': ['app01.throttle.Throttle'],
'DEFAULT_THROTTLE_RATES': {
'xxx': '5/m'}}
--局部禁用
class PublishView(ModelViewSet):
throttle_classes = []
queryset = Publish.objects.all()
serializer_class = PublishSerializer
3.2 频率类代码
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
# 写一个类继承SimpleRateThrottle
class Throttle(SimpleRateThrottle):
# 写一个类属性,scope="xxx"
scope = 'xxx'
# 重写SimpleRateThrottle中的def get_cache_key方法
def get_cache_key(self, request, view):
# 以访问者的ip地址作为识别条件
return request.META.get('REMOTE_ADDR')
# 配置文件中
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': ['app01.throttle.Throttle'],
'DEFAULT_THROTTLE_RATES': {
'xxx': '5/m'
}
}
3.3 频率类的源码简单分析
# 源码分析:
-之前咱们读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(到了频率的限制)
4. 鸭子类型
# 指的是面向对象中,子类不需要显示的继承某个类,只要有某个的方法和属性,那就属于这个类的同一种类型。
--假设有个鸭子类Duck类,有两个方法,run,speak方法
--假设又有一个普通鸭子类,PDuck,如果它也是鸭子,它需要继承Duck类,只要继承了鸭子类,什么都不需要写,普通鸭子类的对象就是鸭子这种类型;如果不继承,普通鸭子类的对象就不是鸭子这种类型
# python不推崇这个,它推崇鸭子类型,指的是
不需要显示的继承某个类,只要我的类中有run和speak方法,我就是鸭子这个类
# 有小问题:如果使用python鸭子类型的写法,如果方法写错了,它就不是这个类型了,会有问题
# python为了解决这个问题:
-方式一:abc模块,装饰后,必须重写方法,不重写就报错
-方式二:drf源码中使用的:父类中写这个方法,但没有具体实现,直接抛异常
练习
继承BaseThrottle编写频率类
class OurThrottle(BaseThrottle):
aaa = {}
def allow_request(self, request, view):
#(1)取出访问者ip
user_ip = request.META.get('REMOTE_ADDR')
#(2)判断当前ip不在访问字典里,
if user_ip not in self.aaa:
bbb = time.time() # 获取当前时间
self.aaa[user_ip] = [] # 将访问者添加到字典里
self.aaa.get(user_ip).append(bbb) # 将当前访问者的访问时间添加进去
# 直接返回True,表示第一次访问
return True
else: # 在字典里
# 循环判断当前ip的列表,有值
for i in self.aaa.get(user_ip):
# 获取当前时间
ccc = time.time()
# 当前时间减去列表的中时间
ddd = ccc - i
# 判断列表中的时间是否大于
if ddd > 60: # 控制的时间
index = self.aaa.get(user_ip).index(i)
self.aaa.get(user_ip).pop(index)
eee = time.time() # 再次时获取当前时间
# 判断,当列表小于3,说明一分钟以内访问不足三次,
if len(self.aaa.get(user_ip)) < 3:
# 把当前时间插入到列表第一个位置,返回True,顺利通过
self.aaa.get(user_ip).append(eee)
return True
else:
# 当大于等于3,说明一分钟内访问超过三次,返回False验证失败
return False