描述
针对同一个 view
,对不同的接口设置不同的权限
看了 DRF views
源码:
def initial(self, request, *args, **kwargs):
"""
Runs anything that needs to occur prior to calling the method handler.
"""
self.format_kwarg = self.get_format_suffix(**kwargs)
# Perform content negotiation and store the accepted info on the request
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
# Determine the API version, if versioning is in use.
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme
# Ensure that the incoming request is permitted
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
通过源码可以发现,每次请求进来,都要做认证,权限验证和限流验证。如果所有接口都需要权限,这直接在视图类中直接设置 permission_classes
即可;如果针对业务中部分接口需要权限,其他不需要权限的场景,这样一刀切的方式是行不通的,因为进来的请求会被权限打回去,针对部分接口需要权限的场景,可能需要变通一下。
1 场景如下:例如个人博客
例如有个分类模型
class Category(models.Model):
name = models.CharField('名称', max_length=30)
created = models.DateTimeField('生成时间', auto_now_add=True)
def __str__(self):
return self.name
如果使用 DRF 的 viewset
class CategoryViewSet(viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
DRF 会自动生成六个方法,具体就不说了,对于个人博客来说,获取列表以及详情是不需要设置权限的,但是对于更新,创建,删除时需要做权限验证的,那么问题来了,我该怎么做好权限验证,这里我假使获取详情需要管理员权限,其他方法都不需要权限验证
2 变通方式
方法 1:自定义装饰器
写包装权限的装饰器
from functools import update_wrapper
def wrap_permission(*permissions, validate_permission=True):
"""custom permissions for special route"""
def decorator(func):
def wrapper(self, request, *args, **kwargs):
self.permission_classes = permissions
if validate_permission:
self.check_permissions(request)
return func(self, request, *args, **kwargs)
return update_wrapper(wrapper, func)
return decorator
自定义权限类
from rest_framework.permissions import IsAdminUser
class IsVbAdminUser(IsAdminUser):
"""
Allows access only to admin users.
"""
def has_object_permission(self, request, view, obj):
"""
Return `True` if permission is granted, `False` otherwise.
"""
return self.has_permission(request, view)
然后在 viewset 中,例如获取详情接口,这样用,
@wrap_permission(IsVbAdminUser)
def retrieve(self, request, *args, **kwargs):
如果直接请求,就会下面错误
"error_message": "Authentication credentials were not provided."
后面在 DRF 的装饰器中找到 permission_classes
def permission_classes(permission_classes):
def decorator(func):
func.permission_classes = permission_classes
return func
return decorator
这个默认的业务还需要定制,根据自己业务需要了
方法2:使用 detail_route 或者 list_route
@detail_route(
url_path='test',
permission_classes=(IsVbAdminUser, ),
)
def test(self, request, *args, **kwargs):
这样耍的原因,在看了 DRF route 源代码,发现
method_kwargs = getattr(viewset, methodname).kwargs
initkwargs = route.initkwargs.copy()
initkwargs.update(method_kwargs)
是不是很好玩
对于自定义路由,使用这种方法,还是蛮方便的。
方法 3:重新定义 initial 方法
重写 initial 方法,根据方法定义不同的权限类
添加 permission_classes_map
类属性
permission_classes_map
定义接口和权限的映射,用法如下:
permission_classes_map = {
方法名: 权限类列表
}
此为特定接口的权限检测,例如如果视图中包含 create 方法,同时在又在视图中设置了全局性的 permission_classes,
但是想为 create 定义不同于全局的权限,所以这里可以这样设置,示例如下:
permission_classes_map = {
'create': [CustomPermission]
}
permission_classes_map = {}
def initial(self, request, *args, **kwargs):
"""重新定义此方法,添加灵活配置权限映射"""
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
if hasattr(handler, '__name__'):
handler_name = handler.__name__
elif hasattr(handler, '__func__'):
handler_name = handler.__func__.__name__
else:
handler_name = None
if handler_name and handler_name in self.permission_classes_map:
if isinstance(self.permission_classes_map.get(handler_name), (tuple, list)):
self.permission_classes = self.permission_classes_map.get(handler_name)
return super(CommonWithSignViewSet, self).initial(request, *args, **kwargs)
方法 4:使用不同的视图
把需要权限验证的和不需要权限验证的视图分开,写两个视图,但是这样做会冗余部分代码
3 小结
我在以往的经历中,如果是针对默认的路由需要加权限验证,我会使用方法 1,对于自定义路由,我会使用方法 2,当然也可以反过来思考,设置全局的权限验证,如果那个方法不需要权限验证,使用装饰器把权限设置为空即可,随便怎么折腾
前两种方法尽量不要再视图属性中设置 permission_classes
,这样的处理有点奇葩,个人可以针对 DRF 处理流程,进行包装,看业务需要。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。