-drf-认证权限频率
一 认证Authentication
1 自定义认证方案
1.1 编写models
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
class UserToken(models.Model):
user = models.OneToOneField(to='User', on_delete=models.CASCADE)
token = models.CharField(max_length=64)
1.2 编写登录认证类
class TokenAuth():
def authenticate(self, request):
token = request.data.get('token')
# token生成后再数据库保存一份,再发送到客户端,存到cookie中,如果是APP则独立存在
# 然后每次登陆都会带着这个token前来校验
token_obj = models.UserToken.objects.filter(token=token)
# 用户登录时该用户自带的token校验是否存在,因为token唯一所以有则成功不会匹配到重复的
if token_obj:
return # 可以是一个元组
else:
raise AuthenticationFailed('该用户不存在')
def authenticate_header(self, request):
pass # 报相关错误信息
'''
注意:
1.认证类,认证通过可以返回一个元组,有两个值,第一个值会给request.user,第二个值会给request.auth
2.认证类可以配置多个,按照从前向后的顺序执行,如果前面有返回值则结束
'''
1.3 编写视图
def get_random(name):
import hashlib
import time
md = hashlib.md5()
md.update(bytes(str(time.time()), encoding='utf-8'))
md.update(bytes(name, encoding='utf-8'))
# 通过唯一时间戳以及唯一username加密得到唯一的token值
return md.hexdigest()
class Register(APIView):
def post(self, request):
res = {'status': 100, 'msg': '保存成功'}
# 反序列化称为一个对象
# print(request.data['publish_id'])
ser = serializer.UserModelSerializer(data=request.data)
if ser.is_valid():
ser.save()
res['result'] = ser.data # 序列化输出
else:
res['code'] = 109
res['msg'] = str(ser.errors)
return Response(res)
class Login(APIView):
def post(self, request):
res = {'status': 100, 'msg': '登录成功'}
username = request.data.get('username')
password = request.data.get('password')
user = models.User.objects.filter(username=username, password=password)
if user:
user = user.first()
token = get_random(username)
models.UserToken.objects.update_or_create(user=user,
defaults={'token':token})
# 当前登录用户有token,则是更新token的操作,如果没有token,则是创建token并且与相应 用户进行一对一关联的操作
res['token'] = token
else:
res['status'] = 101
res['msg'] = '登录失败'
return Response(res)
class Course(APIView):
authentication_classes = [TokenAuth, ] # 登录认证,并且校验了token,可以放多个认证
def get(self, request):
return Response({'msg': '查看课程成功'})
def post(self, request):
return Response({'msg': '创建课程成功'})
创建登录成功
1.4 全局使用
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": ["app10.myauth.TokenAuth", ]
}
# 局部禁用即局部不用校验
# 在视图类下定义一个
authentication_classes = []
# 相当于重写了校验,然后设置为空不需要校验
# 注意:全局时TokenAuth不要写在views,写入另外一个文件
1.5 局部使用
#局部使用,只需要在视图类里加入:
authentication_classes = [TokenAuth, ]
2 内置认证方案(需要配合权限使用)
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication', # session认证
'rest_framework.authentication.BasicAuthentication', # 基本认证
)
}
也可以在每个视图中通过设置authentication_classess属性来设置
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.views import APIView
class ExampleView(APIView):
# 类属性
authentication_classes = [SessionAuthentication, BasicAuthentication]
...
认证失败会有两种可能的返回值:
- 401 Unauthorized 未认证
- 403 Permission Denied 权限被禁止
二 权限Permissions
权限控制可以限制用户对于视图的访问和对于具体数据对象的访问
- 在执行视图的dispatch()方法前(实例化request对象),会进行视图访问权限的判断
- 在通过get_object()获取具体对象时,会进行模型对象访问权限的判断
登录成功以后,超级用户可以干某些事,普通用户不能干。超级用户可以查看某些接口,普通用户不能
注意:
权限校验是在登录校验的基础之上,不然会因为在一个'AnonymousUser' object对象中找不到校验权限的字段而报错
如下:
AttributeError: 'AnonymousUser' object has no attribute 'user_type'
1 自定义权限
1.1 编写权限类
class SuperPermission(BaseAuthentication):
message = '不是超级用户,没有权限访问' # 重写message,本来是英文重写成中文提示
def has_permission(self, request, view):
# 权限在认证之后所以能取到request.user
if request.user.user_type == '1':
return True
else:
return False
1.2 全局使用
REST_FRAMEWORK = {
"DEFAULT_PERMISSION_CLASSES": ["app10.MyAuth.SuperPermission", ]
}
-局部经用,部分视图类没有权限限制
permission_classes = [] # 重写为空即可
1.3 局部使用
# 局部使用只需要在视图类里加入:
permission_classes = [MyAuth.SuperPermission]
1.4 说明
如需自定义权限,需继承rest_framework.permissions.BasePermission父类,并实现以下两个任何一个方法或全部
- `.has_permission(self, request, view)`
是否可以访问视图, view表示当前视图对象
- `.has_object_permission(self, request, view, obj)`
是否可以访问数据对象, view表示当前视图, obj为数据对象
2 内置权限
2.1 内置权限类
from rest_framework.permissions import AllowAny,IsAuthenticated,IsAdminUser,IsAuthenticatedOrReadOnly
- AllowAny 允许所有用户
- IsAuthenticated 仅通过认证的用户
- IsAdminUser 仅管理员用户
- IsAuthenticatedOrReadOnly 已经登陆认证的用户可以对数据进行增删改操作,没有登陆认证的只能查看数据。
2.2 全局使用
可以在配置文件中全局设置默认的权限管理类,如
REST_FRAMEWORK = {
....
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}
如果未指明,则采用如下默认配置
REST_FRAMEWORK = {
....
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
)
}
2.3 局部使用
也可以在具体的视图中通过permission_classes属性来设置,如
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
class ExampleView(APIView):
permission_classes = (IsAuthenticated,)
...
三 限流Throttling
可以对接口访问的频次进行限制,以减轻服务器压力。
一般用于付费购买次数,投票以及反爬等场景使用
1 自定义频率类
1.1 编写频率类
# 自定义的逻辑
#(1)取出访问者ip
#(2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
#(3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
#(4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
#(5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
class MyThrottles():
VISIT_RECORD = {}
def __init__(self):
self.history=None
def allow_request(self,request, view):
#(1)取出访问者ip
# print(request.META)
ip=request.META.get('REMOTE_ADDR')
import time
ctime=time.time()
# (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问
if ip not in self.VISIT_RECORD:
self.VISIT_RECORD[ip]=[ctime,]
return True
self.history=self.VISIT_RECORD.get(ip)
# (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,在源码中这个并没有写死,而是通过
'''
if rate is None:
return (None, None)
num, period = rate.split('/')
num_requests = int(num)
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
'''
# 从配置中获得了限制时间
while self.history and ctime-self.history[-1]>60: # duration
self.history.pop()
# (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
# (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
# 这个限制访问次数也是通过
'''
if len(self.history) >= self.num_requests:
return self.throttle_failure()
return self.throttle_success()
'''
if len(self.history)<3: # 相当于配置的request_num
self.history.insert(0,ctime)
return True
else:
return False
def wait(self):
import time
ctime=time.time()
return 60-(ctime-self.history[-1])
# 所以在源码中相当于是把访问时间以及访问次数写活了,
# 然后直接在setting中配置即可
1.2 全局使用
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES':['app01.utils.VisitThrottles',],
}
1.3 局部使用
#在视图类里使用
throttle_classes = [MyThrottles,]
2 内置频率类
2.1 根据用户ip限制
#写一个类,继承自SimpleRateThrottle,(根据ip限制)
class VisitThrottle(SimpleRateThrottle):
scope = 'luffy'
def get_cache_key(self, request, view):
return self.get_ident(request) # 返回xxf或是addr
# 在setting里配置:(一分钟访问三次)
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES':{ # 全局使用继续在此字典中写即可
'luffy':'3/m' # key要跟类中的scop对应
}
}
# 可以全局使用,也可以局部使用
# 在setting.py中配置,全局使用
'DEFAULT_THROTTLE_CLASSES':['app10.myauth.VisitThrottles',]
# 局部禁用,为空即可
throttle_classes = []
# 局部使用
# 在视图下定义
throttle_classes = [myauth.VisitThrottle, ]
2.2 返回中文信息
class BookInfo(ModelViewSet):
queryset = models.Book.objects.all()
serializer_class = serializer.BookModelSerializer
authentication_classes = [myauth.TokenAuth, ]
throttle_classes = [myauth.VisitThrottle, ]
def throttled(self, request, wait):
from rest_framework.exceptions import Throttled
class VisitThrottled(Throttled):
class MyThrottled(Throttled):
default_detail = '你被限制了,宝贝'
extra_detail_singular = f'还有 {int(wait)} second.'
# 限制之后剩余的时间
extra_detail_plural = f'已经用了 {int(wait)} seconds.'
# 使用了多长时间被限制
raise MyThrottled(wait)
2.3 限制匿名用户(根据ip限制)
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': ( # 全局使用,可以注释然后局部使用
'rest_framework.throttling.AnonRateThrottle',
),
'DEFAULT_THROTTLE_RATES': { # 配置的匿名用户,访问时间与次数的限制
'anon': '3/m',
}
}
# 使用 `second`, `minute`, `hour` 或`day`来指明周期。
# 可以全局使用,局部使用
2.4 限制登录用户(根据id限制)
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': ( # 全局使用,可以注释然后局部使用
'rest_framework.throttling.UserRateThrottle'
),
'DEFAULT_THROTTLE_RATES': { # 配置的登录用户,访问时间与次数的限制
'user': '10/m'
}
}
# 可以全局使用,局部使用
2.5 其他
1) AnonRateThrottle
限制所有匿名未认证用户,使用IP区分用户。
使用DEFAULT_THROTTLE_RATES['anon']
来设置频次
2)UserRateThrottle
限制认证用户,使用User id 来区分。
使用DEFAULT_THROTTLE_RATES['user']
来设置频次
3)ScopedRateThrottle
限制用户对于每个视图的访问频次,使用ip或user id。
例如:
class ContactListView(APIView):
throttle_scope = 'contacts'
...
class ContactDetailView(APIView):
throttle_scope = 'contacts'
...
class UploadView(APIView):
throttle_scope = 'uploads'
...
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': (
'rest_framework.throttling.ScopedRateThrottle',
),
'DEFAULT_THROTTLE_RATES': {
'contacts': '1000/day',
'uploads': '20/day'
}
}
模型层choice字段使用
# 在模型类
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
user_type = models.CharField(max_length=1, default='0')
gender = models.SmallIntegerField(choices=((1, '男'), (2, '女'), (3, '未知')), default=3)
# 在视图类中,在序列化类中
-get_字段名_dispaly()的方法,该方法获得choice字段对应的数据
class UserModelSerializer(serializers.ModelSerializer):
gender = serializers.CharField(source='get_gender_display')
# 重写gender字段,然后用source执行此函数
class Meta:
model = models.User
fields = '__all__'
练习:在一个接口中实现登录用户与匿名用户不同的限制
需要处理登录认证才能查看接口然后匿名用户也能查看接口的矛盾,那就是认证校验如果不通过自定义不要报错,return None,如果校验通过则request.user有值,一但有值,则不经过ip的限制,而是经过user.id的限制,即不走匿名用户限制的接口,走登录用户限制的接口。
# auth.py
from rest_framework.authentication import BaseAuthentication
from rest_framework.throttling import SimpleRateThrottle
class TokenAuth(BaseAuthentication):
def authenticate(self, request):
token = request.data.get('token')
token_obj = models.UserToken.objects.filter(token=token)
# 用户登录时该用户自带的token校验是否存在
if token_obj:
token_obj = token_obj.first()
return (token_obj.user, token_obj) # 可以是一个元组,user与auth接收,形成了request.user方便进行权限校验
else:
return None # 不报错,只起了赋值给request.user的作用
class VisitThrottle(SimpleRateThrottle):
scope = 'luffy'
def get_cache_key(self, request, view):
if not request.user.id:
return self.get_ident(request) # 如果没有登录用户就是以Ip限制
else:
return None #
class LoginThrottle(SimpleRateThrottle):
scope = 'login'
def get_cache_key(self, request, view):
return request.user.id
# 直接以user_id为限制,如果没有登录则是返回None,代表没有进行此操作
# setting.py
# 进行相关配置
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'luffy': '3/m', # key要跟类中的scop对应,自定义限制访问
'login': '10/m'
}
}
# views.py
# 实现一个接口能够识别登录与匿名的状态,从而产生不同的限制
class PublishInfo(ModelViewSet):
queryset = models.Publish.objects.all()
serializer_class = serializer.PublishModelSerializer
authentication_classes = [myauth.TokenAuth, ]
# 先进行登录认证,但是不会报错,方便继续进行user_id的限制访问
throttle_classes = [myauth.VisitThrottle, myauth.LoginThrottle]