目录
-认证组件
--认证简介
只有认证通过的用户才能访问指定的url地址,比如:查询课程信息,需要登录之后才能查看,没有登录,就不能查看,这时候需要用到认证组件
--使用方法
在app下新建一个.py文件,在其中写一个认证类(必须继承BaseAuthentication),在该类中重写authenticate方法,认证逻辑都写在其中。若认证通过,返回两个值;认证失败,抛出异常APIException或AuthenticationFailed(继承的是APIException,一般都用它)。
--源码分析
APIView-->dispatch()方法-->request = self.initialize_request(request, *args, **kwargs)-->
initialize_request()返回Request()对象,里面的authenticators从get_authenticators()中得到-->
get_authenticators(),返回return [auth() for auth in self.authentication_classes],列表生成
式,从配置的authentication_classes中一个个取类加括号生成对象-->authentication_classes在APIView
类最初配置的authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES-->搞清楚
authenticators从哪来的后-->再看dispatch方法中的关键-->self.initial(request, *args, **kwargs)
-->initial()中有三大组件——认证、权限、频率:
# 三大组件-认证 校验用户:游客、合法用户、非法用户
# 游客:代表校验通过,直接进入下一步校验(权限校验)
# 合法用户:代表校验通过,将用户存在request.user中,再进入下一步校验(权限校验)
# 非法用户:代表校验失败,抛出异常,返回403forbidden
self.perform_authentication(request)
# 三大组件-权限 校验用户权限:必须登录、所有用户、登录读写游客只读、自定义用户角色
# 认证通过:可以进入下一步校验
# 认证失败:抛出异常403
self.check_permissions(request)
# 三大组件-频率 限制视图接口被访问的频率数:限制条件(ip,id)、频率周期时间(s,m,h)、频率次数(几次/s)
# 没有达到限次:正常访问接口
# 达到限次:限制时间内不能访问,限制时间后可以访问
self.check_throttles(request)
-->先看认证perform_authentication()-->只有一句话request.user-->此user来自drf的Request,应去
drf的Request类中找user属性/方法-->点击Ctrl+user去drf的Request-->self._authenticate()
核心:
def _authenticate(self):
for authenticator in self.authenticators:
# authenticators是可迭代对象,是一个列表,里面是配置的认证类加括号产生的对象
# 拿到一个个认证器,进行认证
try:
# 认证器调用认证方法authenticate(自己的认证类重写的)
# 该方法应返回一个包含user,auth的元组
# 该方法被try包裹,代表该方法会抛异常,抛异常即为认证失败
user_auth_tuple = authenticator.authenticate(self) #self是request对象的
except exceptions.APIException:
self._not_authenticated()
raise
# 返回值的处理
if user_auth_tuple is not None:
self._authenticator = authenticator
# 如果有返回值,就将登录用户 与 登录认证 解压赋值到request.user request.auth中
self.user, self.auth = user_auth_tuple
return
# 如果返回值user_auth_tuple为空,代表认证通过,但是没有登录用户与登录认证信息,表名这是游客
self._not_authenticated()
--使用
models.py
'''首先新建一个用户表,用于校验登录信息。这自己写了个token,并单独建了一个和User一对一关联的UserToken表'''
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
user_type = models.IntegerField(choices=((1,'超级用户'), (2,'普通用户'), (3,'游客')))
class UserToken(models.Model):
token = models.CharField(max_length=64)
user = models.OneToOneField(to='User', on_delete=models.CASCADE)
auth_user.py
'''我在app下新建了user_auth.py文件,里面写认证类'''
from rest_framework.authentication import BaseAuthentication
from app01 import models
from rest_framework.exceptions import AuthenticationFailed
class MyAuthentication1(BaseAuthentication):
# 重写authenticate方法
def authenticate(self, request):
token = request.GET.get('token') # token一般放在浏览器中,这为了演示方便把它放在url?后
if token:
user_token_obj = models.UserToken.objects.filter(token=token).first()
if user_token_obj:
# 认证通过 返回两个值
return user_token_obj.user, token
else:
# 认证失败 抛异常
raise AuthenticationFailed('认证失败')
else:
raise AuthenticationFailed('请求中未携带token认证信息!')
views.py
# 登录视图类
from rest_framework.views import APIView
from rest_framework.response import Response
from app01 import models
import uuid
class LoginAPIView(APIView):
# 登录发的是post请求
def post(self, request):
username = request.data.get('username')
password = request.data.get('password')
user_obj = models.User.objects.filter(username=username,password=password).first()
if user_obj:
# 登录成功, 生成一个随机字符串
token = uuid.uuid4() # 生成随机token,利用uuid模块,几乎不会重复
# 将token存到UserToken表中
# 此方法每次登录都会存一个token,既耗表资源也不恰当
# models.UserToken.objects.create(token=token, user=user_obj)
# 我们需要的是一个用户就一行表资源,每次登录将token更新即可,
# 而一次也未登录的应该生成token存在UserToken表中
models.UserToken.objects.update_or_create(defaults={'token':token}, user=user_obj)
# update_or_create(self, defaults=None, **kwargs)使用给定的kwargs查找一个对象,如果对象存在,则用默认值更新它,否则创建一个新对象。
return Response({'status':100,'msg':'登录成功','token':token})
else:
return Response({'status':101,'msg':'用户名或密码错误'})
认证类使用顺序:先用视图类中的验证类,再用settings里配置的验证类,最后用默认的验证类
---局部使用
局部使用,只需要在视图类里加入:
authentication_classes = [MyAuthentication1, ]
from rest_framework.viewsets import ModelViewSet
from app01.user_auth import MyAuthentication1
class PublishModelViewSet(ModelViewSet):
authentication_classes = [MyAuthentication1]
queryset = models.Publish.objects
serializer_class = PublishSerializers
'''
此时get请求访问所有:
http: //127.0.0.1:8000/publishes/?token=12a3bb54-fe89-4451-ae1a-b3891659b523
'''
---全局使用
'''所有视图类都认证,去setting.py中配置:'''
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES':['app01.user_auth.MyAuthentication1',]
} # 是列表,可多个
---局部禁用
'''对配了全局的又想某些视图类不用,则在该视图类中重赋值为空'''
authentication_classes = []
-权限组件
之前的auth模块里自带权限校验is_staff(),这学的是drf的我们自己写的权限校验,但其实drf也有对应方法支持之前的auth模块带的权限校验,后面会有
--使用方法
在.py文件中新建一个类,继承BasePermission,重写has_permissioin方法,如果权限通过返回True;权限不通过返回False。
--源码分析
APIView-->dispatch()方法-->self.initial(request, *args, **kwargs)-->
self.check_permissions(request)-->def check_permissions(self, request)
-----核心:
def check_permissions(self, request):
# 遍历权限对象列表得到一个个权限对象(权限器),进行权限认证
for permission in self.get_permissions():
# 权限类一定得有个has_permission方法,用来写权限认证逻辑
# 参数:权限对象self,请求对象request,视图类对象view
# 返回值:有权限True,无权限False
if not permission.has_permission(request, self):
self.permission_denied(
request, message=getattr(permission, 'message', None)
)
--使用
user_permission.py
from rest_framework.permissions import BasePermission
class MyPermission1(BasePermission):
# 重写has_permission方法
def has_permission(self, request, view):
# 是超级用户才能访问指定内容
user_obj = request.user # 由于已经认证过了,所以request中有user
print(user_obj.get_user_type_display()) # 显示choices字段对应的中文信息:对象.get_字段名_display()
if user_obj.user_type == 1:
return True
else:
return False
model.py沿用认证里的
urls.py
path('permit1/', views.PublishAPIViewPermissionTest1.as_view()),
path('permit2/', views.PublishAPIViewPermissionTest2.as_view()),
权限类使用顺序:先用视图类中的权限类,再用settings里配置的权限类,最后用默认的权限类
---局部使用
from app01 import user_permission
class PublishAPIViewPermissionTest1(APIView):
authentication_classes = [MyAuthentication1]
permission_classes = [user_permission.MyPermission1]
def get(self, request):
return Response('权限测试,只有超级用户才能看到哦~')
class PublishAPIViewPermissionTest2(APIView):
authentication_classes = [MyAuthentication1]
def get(self, request):
return Response('权限测试,普通用户也能看到哦~')
---全局使用
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES':['app01.user_permission.MyPermission1'], # 权限
} # 注意:在REST_FRAMEWORK里面配置就行了,不能命两个!!!
---局部禁用
permission_classes = []
--内置权限校验
drf内置的权限校验只能是通过admin中设置的用户,因为它用了IsAdminUser等它内置的
# 内置权限IsAdminUser演示
# 1、创建超级管理员createsuperuser
# 2、写一个测试视图类
from rest_framework.permissions import IsAdminUser
from rest_framework.authentication import SessionAuthentication
class TestView(APIView):
authentication_classes = [SessionAuthentication, ]
permission_classes = [IsAdminUser]
def get(self, request):
return Response('superuser可看')
# 3、超级用户登录admin,再访问test就有权限
# 4、正常用户没有权限看
-频率组件
频率即控制某个用户访问某个url的次数,如一分钟内只能三次等
drf内置的频率校验只能操作到admin用户或通过admin后台管理设置的用户
--内置频率类校验使用
drf自带的内置频率校验类有匿名用户类(未登录用户)AnonRateThrottle,登录用户类UserRateThrottle。
---局部使用
在视图类下配置throttle_classes = [AnonRateThrottle] # 内置匿名用户限制
在settings.py中的REST_FRAMEWORK中写'DEFAULT_THROTTLE_RATES':{'anon':'3/m'}
# 限制频率次数,1min(h-hour,y-year…)内只能3次,anon与AnonRateThrottle对应;user与UserRateThrottle对应
---全局使用
在settings.py中配两个参数
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSER':['rest_framework.throttling_UserRateThrottle'],
'DEFAULT_THROTTLE_RATES':{'user':'10/m'} # 必带
}
--自定义频率
自定义频率校验实现逻辑如下,就是要重写allow_request()和wait()两个方法,继不继承BaseThrottle都行因为Python实行的是鸭子类型(我有鸭子的两个方法特征了,我就是鸭子了),但实现的较low,3/60写的死不动态,可查看SimpleRateThrottle类的源码学习思维
'''自定义的逻辑'''
# (1)取出访问者ip
# (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
# (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
# (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
# (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
eg:
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以内的访问时间,
while self.history and ctime-self.history[-1]>60:
self.history.pop()
# (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
# (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
if len(self.history)<3:
self.history.insert(0,ctime)
return True
else:
return False
def wait(self):
import time
ctime=time.time()
return 60-(ctime-self.history[-1])