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 条评论
推荐阅读
Django | 信号使用思考
重拾些许关于信号模块使用的记忆,记录对于 Django 信号使用的思考。本文使用的 Django 的版本是 4.21 源码注释 {代码...} 2 函数清单2.1 _make_id 方法 {代码...} 首先认真分析下其业务实现,target 参数是接收...

青阳半雪阅读 447

基于Sanic的微服务基础架构
使用python做web开发面临的一个最大的问题就是性能,在解决C10K问题上显的有点吃力。有些异步框架Tornado、Twisted、Gevent 等就是为了解决性能问题。这些框架在性能上有些提升,但是也出现了各种古怪的问题难以...

jysong6阅读 3.9k评论 3

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

良许4阅读 2.2k

又一款眼前一亮的Linux终端工具!
今天给大家介绍一款最近发现的功能十分强大,颜值非常高的一款终端工具。这个神器我是在其他公众号文章上看到的,但他们都没把它的强大之处介绍明白,所以我自己体验一波后,再向大家分享自己的体验。

良许5阅读 1.8k

FastAPI性能碾压Flask?
不止一次的听过,FastAPI性能碾压Flask,直追Golang,不过一直没有测试过,今天闲着没事测试一下看看结果。不知道是哪里出了问题,结果大跌眼镜。

二毛erma02阅读 10.1k评论 3

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

良许3阅读 1.8k

Python之如何优雅的重试
为了避免偶尔的网络连接失败,需要加上重试机制,那么最简单的形式就是在对应的代码片段加一个循环,循环体里使用异常捕获,连接成功时退出循环,否则就重复执行相关逻辑,此时修改之后的函数f如下

Harpsichord12073阅读 7.3k

现实与完美之间

1.6k 声望
24 粉丝
宣传栏