drf 06——认证、权限、频率、排序、过滤、RBAC
目录
认证
登录认证《————某些接口必须要登录以后才能访问
登录接口————》登录成功返回随机字符串————》携带随机字符串【认证】通过再继续访问接口
APIView源码————》三大认证实在视图类的方法之前执行的
# 写一个登录接口
用户表 用户token表
前端传入用户名密码--视图类--登录方法--
--校验用户名密码--正确生成随机字符串存入数据库--把随机字符串返回给前端
# 再随便写一个接口 --登录后才能访问
# 写认证类
1.写一个类 继承BaseAuthentication
2.再类中重写authenticate方法
3.在方法中做验证 如果通过--返回两个值
不通过--抛AuthenticationFailed异常
# 使用认证类
局部使用-->视图类中
class BookView(APIView):
authentication_classes = [LoginAuth, ]
全局使用-->配置文件中
REST_FRAMEWORK = {
# 全局使用认证类
'DEFAULT_AUTHENTICATION_CLASSES':['app01.auth.LoginAuth',]
}
局部禁用
class BookView(APIView):
authentication_classes = []
登录接口
# 模型类
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)
class UserToken(models.Model):
token_code = models.CharField(max_length=32)
user = models.OneToOneField(to='User', on_delete=models.CASCADE)
路由
from .views import UserView, BookView
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('user', UserView, 'user')
router.register('book', BookView, 'book')
urlpatterns = [
]
urlpatterns+=router.urls
视图类
class UserView(ViewSet):
authentication_classes = [] # 局部禁用 总不能登录也要先登录才能登录吧
# 127.0.0.1:8080/user/user/login--->post
@action(methods=['post',], detail=False)
def login(self, request):
username = request.data.get('username')
password = request.data.get('password')
user = User.objects.filter(username=username, password=password).first()
if user:
# 登录成功-->生成随机字符串-->uuid能够生成一个随机不重复字符串
token = str(uuid.uuid4())
# 存到UserToken表内 有就更新 没有新建
# defaults=None,**kwargs 根据传入的关键字参数去查 查到就用defaults给的更新 此处为user
UserToken.objects.update_or_create(user=user, defaults={'token_code': token})
return Response({'code': 100, 'msg': '登录成功', 'token_code': token})
else:
return Response({'code': 101, 'msg': '用户名或密码错误'})
认证类
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from .models import UserToken
class LoginAuth(BaseAuthentication):
def authenticate(self, request):
# 做验证 验证用户是否登录 把生成的token放到请求头里
if request.method in ['POST', 'PUT', 'DELETE']:
# 增删改需要登录才能操作
token = request.query_params.get('token')
# 拿到请求头里的token 去数据库查
user_token_obj = UserToken.objects.filter(token_code=token).first()
if user_token_obj:
# 验证通过返回两个值————当前登录用户,token
return user_token_obj.user, token
else:
raise AuthenticationFailed('登录才可以操作哦')
权限
登录成功后 但有的接口区分权限 有权限的人能才能操作
User表的user_type字段来区分权限
# 权限类的写法
1.写一个类 继承BasePermission
2.重写has_permission方法
3.在has_permission中进行权限的判断
有权限返回True,没权限返回False
返回的中文提示信息使用message字段标识
# 权限类的使用
1.局部使用--->视图类中
class BookView(APIView):
permission_classes = [PermissionAuth, ]
2.全局使用--->配置文件
REST_FRAMEWORK = {
# 全局使用认证类
'DEFAULT_PERMISSION_CLASSES':['app01.auth.PermissionAuth',]
}
3.局部禁用
class BookView(APIView):
permission_classes = []
权限类
from rest_framework.permissions import BasePermission
class PermissionAuth(BasePermission):
message = '你没有权限'
def has_permission(self, request, view):
if request.method == 'DELETE':
if request.user.user_type == 3:
print('有权限 删除成功')
return True
else:
print('没权限 删除失败')
self.message = '你是 %s 用户, 不能操作'%request.user.get_user_type_display()
return False
else:
print('没进行删除操作')
return True
频率
# 某个接口,限制访问频率---->可以根据IP,用户id
# 频率类的编写
1.写一个类 继承SimpleRateThrottle
2.重写get_cache_key方法
3.返回什么 就以什么做限制
4.写一个类属性
scope = 'xxx' # 这个属性值用于配置
5.配置文件中配置:
REST_FRAMEWORK = {
# 频率类中scope对应的值
'xxx':'3/m' # 数字/s m h d
}
6.局部使用和全局使用
-局部:视图类中
class BookView(APIView):
throttle_classes = [IPThrottle, ]
-全局:配置文件
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_RATES": {
# 频率类中scope对应的值
'xxx': '3/m' # 数字/s m h d
},
'DEFAULT_THROTTLE_CLASSES':['app01.throttling.IPThrottle', ]
}
频率类
class IPThrottle(SimpleRateThrottle):
# 写一个类属性,字符串
scope = 'xxx'
def get_cache_key(self, request, view):
# return 什么就以什么做限制 返回ip/返回用户id
return request.META.get('REMOTE_ADDR')
# return request.user.id # 返回用户id
视图类
class BookView(APIView):
throttle_classes = [IPThrottle, ]
def get(self, request):
return Response('ok')
def throttled(self, request, wait):
from rest_framework.exceptions import Throttled
class MyThrottled(Throttled):
default_detail = '超限制了'
extra_detail_singular = '还有 {wait} 秒.'
extra_detail_plural = '超出了 {wait} 秒.'
raise MyThrottled(wait)
配置文件
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_RATES": {
# 频率类中scope对应的值
'3_min': '3/m', # 数字/s m h d
'anon':'4/h',
'user':'5/m'
},
'DEFAULT_THROTTLE_CLASSES':['app01.throttling.IPThrottle',]
}
自定义频率类
# 逻辑:
1. 取出访问者ip {192.168.1.12:[访问时间3,访问时间2,访问时间1],192.168.1.12:[],192.168.1.14:[]}
2. 判断ip在不在访问字典内,不在则添加进去 并加入第一次访问时间
3. 不是第一次 则根据ip取出访问时间的列表 循环判断列表不为空,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
4. 判断列表<3 说明访问次数不足三次 将当前时间插到列表第一个位置 返回True 表示通过
5. >=3 说明访问超过三次 返回False验证失败
class MyThrottling():
visit_record = {}
def __init__(self):
self.history = None
def allow_request(self, request, view):
ip = request.META.get('REMOTE_ADDR') # 1.拿到ip
import time
ctime = time.time()
# 2.如果ip不在访问字典里 表示第一次访问 将ip,和第一次访问时间添加进去
if ip not in self.visit_record:
self.visit_record[ip] = [ctime, ]
# {192.168.1.12:[访问时间3,访问时间2,访问时间1],192.168.1.12:[],192.168.1.14:[]}
return True
# 3.不是第一次 则 取出ip对应的时间列表
self.history = self.visit_record.get(ip) # 时间列表[时间3,时间2,时间1]
while self.history and ctime - self.history[-1] > 60: # 将现在时间和列表从后往前比 超过60s的去掉
self.history.pop()
# 4.列表<3 说明访问不足三次 便把当前时间插到列表第一个 返回True
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])
drf内置认证类、权限类、频率类
# 内置的认证---》跟咱们项目都不贴和,咱们不用,咱们自己根据自己的规则写
-SessionAuthentication:之前老的session认证登录方式用,后期都不用了
-BasicAuthentication :基本认证方式,咱们不用
-TokenAuthentication :使用token认证方式,有用,但是咱们也是自己写的
# 内置的权限类
-IsAdminUser :校验是不是auth的超级管理员权限
-IsAuthenticated:后面会用到,验证用户是否登录了,登录了才有权限,没登录就没有权限
-IsAuthenticatedOrReadOnly:知道有这个东西即可
# 内置的频率类
-UserRateThrottle :限制登录用户的频率,需要配置配置文件
-AnonRateThrottle:登录用户不限制,未登录用户限制,需要配置配置文件
认证类、权限类、频率类源码分析
# 研究的第一个点:三大认证的执行顺序
1.APIView--->dispatch--->三大认证
self.initial(request, *args, **kwargs) # 执行三大认证
'self'为视图类的对象----如BookView的对象
2.研究initial方法:执行了三个方法
self.perform_authentication(request) # 认证
self.check_permissions(request) # 权限
self.check_throttles(request) # 频率
# 相关源码
class APIView(View):
def dispatch(self, request, *args, **kwargs):
......
try:
self.initial(request, *args, **kwargs) # 先认证再执行请求
if request.method.lower() in self.http_method_names:
...
def initial(self, request, *args, **kwargs):
......
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
认证类
# 为什么写了认证类,配置在视图类上,就会走认证?
# 入口:认证类怎么执行的---->self.perform_authentication(request)
1.self.perform_authentication源码
def perform_authentication(self, request):
request.user
2.user是新的request类中的 user(属性?方法?)
---->'Request'类内找到user 是一个方法 但加了@property
@property
def user(self):
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate() # 一开始没有_user 于是执行这句
return self._user
3.self._authenticate() ---->self为Request的对象---->执行的是Request的_authenticate方法
def _authenticate(self):
# authenticator配置的认证类的对象
for authenticator in self.authenticators: # 见第四点
# 你配置在视图类上authentication_classes = [你写的认证类,]---->[你写的认证类1(),你写的认证类2()]
try:
# 调用认证类对象的authenticate方法 传了两个参数!一个认证类自己 一个self(Request类的对象)
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException: # 抛的是AuthenticationFailed,捕获的是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
self._not_authenticated()
4.self.authenticators到底是啥?
# 你配置在视图类上authentication_classes = [你写的认证类,]---->[你写的认证类1(),你写的认证类2()]
Reqeust这个类实例化的时候,传入的,如果不传就是空元组
找Request的实例化----> dispatch中包装了新的Request
request = self.initialize_request(request, *args, **kwargs)
authenticators=self.get_authenticators()
# APIView中的get_authenticators
return [auth() for auth in self.authentication_classes]
self.authentication_classes为配在视图类上的认证类列表
点击查看相关源码
# 相关源码
class APIView(View):
def perform_authentication(self, request):
request.user
def get_authenticators(self):
return [auth() for auth in self.authentication_classes]
def initialize_request(self, request, *args, **kwargs):
...
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(), 这里 !!!!!!!!!!
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
def dispatch(self, request, *args, **kwargs):
request = self.initialize_request(request, *args, **kwargs)
class Request:
def __init__(self,request...authenticators=None...):
self._request = request
self.authenticators = authenticators or ()
@property
def user(self):
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate() # 一开始没有_user 于是执行这句
return self._user
def _authenticate(self):
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
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()
权限类
1. 执行权限类 self.check_permissions(request)
def check_permissions(self, request):
for permission in self.get_permissions(): # 配在视图类上的权限类列表对象
if not permission.has_permission(request, self):
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
2. 研究self.get_permissions()
def get_permissions(self):
return [permission() for permission in self.permission_classes]
频率类
1. self.check_throttles(request)
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())
if throttle_durations:
durations = [
duration for duration in throttle_durations
if duration is not None
]
duration = max(durations, default=None)
self.throttled(request, duration)
# 自定义频率类一定要重写allow_request, 返回True就是没有频率显示,返回False就是被频率限制了
# 联想我们写过的继承SimpleRateThrottle的类
SimpleRateThrottle重写了allow_request方法
class SimpleRateThrottle(BaseThrottle):
def __init__(self):
if not getattr(self, 'rate', None):
self.rate = self.get_rate() # self.rate现在是 '3/m'
# 3 60
self.num_requests, self.duration = self.parse_rate(self.rate) # 从parse_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:
return self.THROTTLE_RATES[self.scope]
except KeyError:
msg = "No default throttle rate set for '%s' scope" % self.scope
raise ImproperlyConfigured(msg)
def parse_rate(self, rate):
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]]
return (num_requests, duration)
def allow_request(self, request, view):
# 只要配置文件配了 就有值 在init中
if self.rate is None:
return True
# 现在的唯一字符串 ip地址
self.key = self.get_cache_key(request, view)
if self.key is None:
return True
# [时间2, 时间1]
self.history = self.cache.get(self.key, [])
self.now = self.timer()
while self.history and self.history[-1] <= self.now - self.duration:
self.history.pop() # 把和现在差60s的数据都剔除 self.history只剩60s内的访问时间
if len(self.history) >= self.num_requests: # >=配置的数字 3
return self.throttle_failure() # return False
return self.throttle_success() # 把当前的时间插入 return True
排序
# 排序功能接口只针对于:获取所有接口
# 继承了GenericAPIView的视图类,只要加入,两个类属性
class BookView(GenericViewSet, ListModelMixin):
serializer_class = BookSerializer
queryset = Book.objects.all()
filter_backends = [OrderingFilter, ]
ordering_fields = ['price','id']
# 访问的时候
http://127.0.0.1:8000/api/v1/books?ordering=price # 按price升序
http://127.0.0.1:8000/api/v1/books?ordering=-price # 按price降序
http://127.0.0.1:8000/api/v1/books?ordering=price,id # 先按价格升序排,价格一样,再按id升序排
过滤
1. 内置的过滤使用:不能指定查询哪个字段 '模糊查询'
继承了GenericAPIView的视图类,只要加入,两个类属性
class BookView(GenericViewSet, ListModelMixin):
serializer_class = BookSerializer
queryset = Book.objects.all()
filter_backends = [SearchFilter,]
search_fields=['name','price'] # 按name或price过滤
# 使用
http://127.0.0.1:8000/api/v1/books?search=关键字 # name like xx or price like xx
2. 第三方django-filter: 指定字段精确查询
-安装:pip3 install django-filter
继承了GenericAPIView的视图类,只要加入,两个类属性
from django_filters.rest_framework import DjangoFilterBackend
class BookView(GenericViewSet, ListModelMixin):
serializer_class = BookSerializer
queryset = Book.objects.all()
filter_backends = [DjangoFilterBackend, ]
filter_fields = ['name', 'price']
# 使用
http://127.0.0.1:8000/api/v1/books?name=三国&price=11
3.自定义过滤器---->完成更多查询操作
-写一个类,继承BaseFilterBackend
-重写filter_queryset方法
-配置在视图类中
from .throttling import FilterName
class BookView(GenericViewSet, ListModelMixin):
serializer_class = BookSerializer
queryset = Book.objects.all()
filter_backends = [FilterName, ]
# 既有过滤又有排序
class BookView(GenericViewSet, ListModelMixin):
serializer_class = BookSerializer
queryset = Book.objects.all()
filter_backends = [FilterName, OrderingFilter]
ordering_fields = ['price', ]
# 源码分析,为什么这么配置就生效
-GenericAPIView的方法
def filter_queryset(self, queryset):
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
全局异常处理
drf配置文件中 已经配置了 但不符合我们想要的要求
# drf的配置文件:
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
如果抛了异常 就会执行exception_handler函数
现在我们重写一个函数 抛异常时 执行我们写的函数
# drf默认的异常处理 只处理了drf自己的异常:
所有drf中抛的异常 都有detail,django的异常 抛出很长的xml数据
{
"detail": "it's wrong"
}
# 我们想要的
{
"code": 999,
"msg": "it's wrong"
}
使用步骤
1.写一个函数
from rest_framework.views import exception_handler
from rest_framework.response import Response
def common_exception_handler(exc, context):
# 正常来讲,在这里需要记录日志---》如何在django中记录日志后面讲
# 日志记录,越详细越好:哪个用户(id,ip),在什么时间,执行哪个视图函数时报了错,请求地址是什么
print(context['view']) # 视图类对象
print(context['request']) # 当前请求对象----->可以取出ip,用户id,当前时间,请求地址来
view = context['view']
request = context['request']
print('ip地址为: %s 的用户 访问了:%s 视图类, 报错 请求地址为:%s' % (request.META.get('REMOTE_ADDR'), str(view), request.path))
response = exception_handler(exc, context)
if response: # 这是drf的异常 drf已处理 但不是我们想要的格式{'code': 100, 'msg': ''}
res = Response({'code': 999, 'msg': request.data.get('detail')})
else:
# res=Response({'code':998,'msg':'服务器错误,请联系系统管理员'})
res = Response({'code': 998, 'msg': str(exc)})
return res
2.把函数配置在配置文件中
REST_FRAMEWORK = {
# 自己写的全局异常捕获
'EXCEPTION_HANDLER': 'app01.exception.common_exception_handler',
}
自动生成接口文档
# 后端人员,写好接口,提供接口文档给前端用
# 如何编写接口文档
1. 使用word写,md写---->提交到git上
2.公司有接口文档平台---->后端人员在文档平台录入数据---->公司自己开发,yapi(百度开源),第三方
3.自动生成接口文档---->项目写好了,一键生成接口文档---->一键生成---->导出---->导入到yapi
# drf中自动生成接口文档
-coreapi,swagger(更通用一些,go,java)
-安装 pip3 install coreapi
-在项目中配置
-加入路由:
from rest_framework.documentation import include_docs_urls
urlpatterns = [
path('docs/', include_docs_urls(title='站点页面标题'))
]
-配置文件中配置
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}
-尽管写接口,写注释,会自动生成
RBAC介绍
# RBAC
基于角色的访问控制(Role-Based Access Control)
在RBAC中:
权限与角色相关联 用户通过成为适当角色的成员而获得角色的权限
这就极大地简化了权限的管理。
这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便
# 这种设计,在公司内部系统用的多,对外的系统基本不用
# 权限:真正的权限,比如发工资,招人,申请测试机
# 角色:(组,部门) 角色下有很多员工
# 用户:一个个的人
# 用户和角色关系是多对多,中间表
# 权限和角色关系是多对多,中间表
# 权限和用户的中间表
#前后端分离项目控制权限---》权限类中
#前后端混合
-django框架在公司里用的很多,写内部项目,肯定要用权限控制,用的就是rbac,自己实现一套,django内置了后台管理,自带rbac(admin+auth)
-基于django的admin二次开发
-美化:xadmin(基本不用了,2.x以后django不兼容多,作者弃坑了)
-simpleui:
-django+drf+vue=djagnovueadmin 一款前后端分离的自带rbac的后台管理框架
# django的admin基于rbac
-auth_user # 用户表
-auth_permission # 权限表
-auth_group # 组,角色表
-auth_user_groups # 用户和组中间表
-auth_group_permissions # 组和权限中间表
-auth_user_user_permissions # 用户和权限中间表
补充1
# external libraries
-Python 解释器--内置模块和包:os,sys,json 。。
-import os
-装的第三模块:site-packages中
-真正的位置:python解释器安装路径下的site-package中
-有些第三方模块一安装---》释放可执行文件---》一定要注意,释放的位置释放在环境变量里
# 安装第三方模块
-pychram中图形化界面装-----》必须有pycharm---》换源
-pip install django==2.2.2 -i 国内源
-敲的pip 是哪个解释器的pip 确认好
# 换源
-装第三方模块---》本质就是从远端某个位置拉了一个包 xx.whl--->释放到了python解释器的site-package文件夹下,把可执行文件释放到scripts文件夹下
-用官方源:在国外---》下载慢
-使用国内镜像:豆瓣,阿里,清华
回顾
# http回顾
-http 请求:特点
-1 http基于tcp协议之上的协议---->tcp处于osi七层的传输层 http处于应用层
-2 基于请求响应----必须是客户端发起请求,服务端响应 不能服务端主动推送信息
-3 无状态,无连接 cookie和session
-http协议有版本
-0.9 1.1 2.x:多路复用 3.x
-http分请求协议和响应协议
-请求协议
-请求首行: 请求方式,请求地址(get地址中数据),协议版本
-请求头: key:value 客户端类型,客户端ip地址,请求编码,cookie...
-请求体: 所谓的body体,posy,put请求真正携带的数据
-urlencoded: name=lqz&age=19&gender=male
-json : {"name":"lqz","age":19,"gender":"male"}
-form-data: 两部分:数据部分,文件部分中间用 -----------分割
-响应协议:
-响应首行: 协议版本,响应状态码,响应状态短语
-响应头: 响应编码格式,cookie...
-响应体: html,json,xml.....
# websocket协议 应用层协议
-服务端主动向客户端推送的情况,使用websocket
# http请求 轮询
# http请求 长轮询:发过去,等一会,再回来
# websocket 协议主动推送
补充2
python 动态强类型语言
go 静态强类型语言
java 静态强类型语言 ---隐式类型转换
js 动态弱类型语言
# 动态:解释性语言
# 强弱:
数据类型强:不同类型之间不允许直接运算
数据类型弱:不同类型之间不需要转换可以直接运算
补充3
# 鸭子类型(面试重点)
-接口概念:规范子类的行为 你当成:父类,有一些方法 ---》Duck类,speak,run方法
-只要子类,继承了Duck,我们就认为,这些子类,他们是同一类,都是鸭子Duck这个类
-python不推崇这个,推崇鸭子类型
-现在只要有一个类,中有speak和run 方法 ,这个类就是鸭子类
-abc装饰器,装饰父类中的方法,子类必须实现该方法,如果不实现,就抛异常---》正好违背了鸭子类型
-djagno中常用的方式,父类抛异常的方式,子类必须重写该方法:
def authenticate(self, request):
raise NotImplementedError(".authenticate() must be overridden.")
# 函数
def add(a:int,b:int):->int
return a+b
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?