DRF | 针对指定的接口设置权限

描述

针对同一个 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 处理流程,进行包装,看业务需要。


黑月亮
点滴记录,步步成长

现实与完美之间

1.6k 声望
24 粉丝
0 条评论
推荐阅读
centos | 修改静态 IP
设置 Centos 为使用静态 IP1 修改网络配置 {代码...} 修改后的内容如下 {代码...} 2 重启网络服务 {代码...} 3 查看地址 {代码...} 参考来源:[链接]

青阳半雪阅读 1.8k评论 3

数据结构与算法:二分查找
一、常见数据结构简单数据结构(必须理解和掌握)有序数据结构:栈、队列、链表。有序数据结构省空间(储存空间小)无序数据结构:集合、字典、散列表,无序数据结构省时间(读取时间快)复杂数据结构树、 堆图二...

白鲸鱼9阅读 5.3k

滚蛋吧,正则表达式!
你是不是也有这样的操作,比如你需要使用「电子邮箱正则表达式」,首先想到的就是直接百度上搜索一个,然后采用 CV 大法神奇地接入到你的代码中?

良许3阅读 1.5k

搭个ChatGPT算法模型,从哪开始?
最近 ChatGPT 很火,火到了各行各业。记得去年更多的还是码农最新体验后拿它搜代码,现在各行各业都进来体验,问它咋理财、怎么写报告和给小孩起名。😂 也因此让小傅哥在头条的一篇关于 ChatGPT 的文章都有了26万...

小傅哥6阅读 1.2k

封面图
程序员适合创业吗?
大家好,我是良许。从去年 12 月开始,我已经在视频号、抖音等主流视频平台上连续更新视频到现在,并得到了不错的评价。每个视频都花了很多时间精力用心制作,欢迎大家关注哦~考虑到有些小伙伴没有看过我的视频,...

良许3阅读 1.3k

Ubuntu20.04 从源代码编译安装 python3.10
Ubuntu 22.04 Release DateUbuntu 22.04 Jammy Jellyfish is scheduled for release on April 21, 2022If you’re ready to use Ubuntu 22.04 Jammy Jellyfish, you can either upgrade your current Ubuntu syste...

ponponon1阅读 4.5k评论 1

PyCharm 激活破解教程, 2023 年 2 月亲测有用
本文分享一下PyCharm 2022.2.3 版本最新激活破解教程,注意不要使用太新的版本,都是 Jetbrains 产品,本文专门配上了 Pycharm 的图片,跟着下面教程一步一步来即可。

程序员徐公阅读 8.4k评论 1

现实与完美之间

1.6k 声望
24 粉丝
宣传栏