Django REST Framework - 每个方法的单独权限
Posted
技术标签:
【中文标题】Django REST Framework - 每个方法的单独权限【英文标题】:Django REST Framework - Separate permissions per methods 【发布时间】:2013-11-15 10:25:40 【问题描述】:我正在使用 Django REST Framework 编写 API,我想知道在使用基于类的视图时是否可以为每个方法指定权限。
Reading the documentation 我发现如果你正在编写基于函数的视图,这很容易做到,只需在你想要使用权限保护的视图的函数上使用 @permission_classes
装饰器。但是,在将 CBV 与 APIView
类一起使用时,我看不到这样做的方法,因为我随后使用 permission_classes
属性指定了完整类的权限,但这将应用于所有类方法(get
、post
、put
...)。
那么,是否可以使用 CBV 编写 API 视图,并为视图类的每个方法指定不同的权限?
【问题讨论】:
如何为每个视图创建一个单独的视图?或者您可以覆盖视图中的 get/post/put 方法并编写您自己的权限。 【参考方案1】:权限应用于整个 View 类,但您可以在授权决策中考虑请求的各个方面(例如 GET 或 POST 等方法)。
以内置IsAuthenticatedOrReadOnly
为例:
SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']
class IsAuthenticatedOrReadOnly(BasePermission):
"""
The request is authenticated as a user, or is a read-only request.
"""
def has_permission(self, request, view):
if (request.method in SAFE_METHODS or
request.user and
request.user.is_authenticated()):
return True
return False
【讨论】:
抱歉耽搁了。谢谢,凯文。你的回答很完美。有IsAuthenticatedOrReadOnly
权限类,可以使用SAFE_METHODS
。
非常好,也适用于 POST - 仅限 API,例如用于由第三方关联公司创建潜在客户,但要防止列出整个潜在客户列表?
在 Django 3+ 中(不知道以前的版本),request.user.is_authenticated()
实际上是request.user.is_authenticated
。 is_authenticated
不是(不再是?)一种方法,而是一个布尔值【参考方案2】:
我在使用 CBV 时遇到了同样的问题,因为我有相当复杂的权限逻辑,具体取决于请求方法。
我想出的解决方案是使用此页面底部列出的第三方“rest_condition”应用程序
http://www.django-rest-framework.org/api-guide/permissions
https://github.com/caxap/rest_condition
我只是拆分权限流逻辑,以便每个分支都会根据请求方法运行。
from rest_condition import And, Or, Not
class MyClassBasedView(APIView):
permission_classes = [Or(And(IsReadOnlyRequest, IsAllowedRetrieveThis, IsAllowedRetrieveThat),
And(IsPostRequest, IsAllowedToCreateThis, ...),
And(IsPutPatchRequest, ...),
And(IsDeleteRequest, ...)]
因此,“Or”根据请求方法确定应该运行权限的哪个分支,而“And”封装了与接受的请求方法相关的权限,因此所有权限都必须通过才能被授予。您还可以在每个流程中混合使用“或”、“与”和“非”来创建更复杂的权限。
运行每个分支的权限类如下所示,
class IsReadyOnlyRequest(permissions.BasePermission):
def has_permission(self, request, view):
return request.method in permissions.SAFE_METHODS
class IsPostRequest(permissions.BasePermission):
def has_permission(self, request, view):
return request.method == "POST"
... #You get the idea
【讨论】:
也可以试试这个:github.com/Pithikos/rest-framework-roles DRF 添加了与此类似的功能:django-rest-framework.org/api-guide/permissions/… "如果它们继承自 rest_framework.permissions.BasePermission,则可以使用标准 Python 位运算符组成权限...注意:t 支持 & (and) , |(或)和~(非)。”所以你可以写permission_classes = [IsAlienFromSpace & IsFriendly, IsAuthenticated | ReadOnly]
之类的权限【参考方案3】:
2020 年 3 月 30 日更新:我原来的解决方案只修补了 object 权限,而不是请求权限。我在下面包含了一个更新,以使这项工作也适用于请求权限。
我知道这是一个老问题,但我最近遇到了同样的问题并想分享我的解决方案(因为接受的答案并不是我所需要的)。 @GDorn 的回答让我走上了正轨,但它只适用于 ViewSet
s,因为 self.action
我已经通过创建自己的装饰器解决了这个问题:
def method_permission_classes(classes):
def decorator(func):
def decorated_func(self, *args, **kwargs):
self.permission_classes = classes
# this call is needed for request permissions
self.check_permissions(self.request)
return func(self, *args, **kwargs)
return decorated_func
return decorator
我的装饰器没有像内置装饰器那样在函数上设置permission_classes
属性,而是包装调用并在被调用的视图实例上设置权限类。这样,普通的get_permissions()
不需要任何更改,因为它只依赖于self.permission_classes
。
要使用请求权限,我们确实需要从装饰器中调用check_permission()
,因为它最初是在initial()
中调用的,所以在修补permission_classes
属性之前。
注意通过装饰器设置的权限是唯一调用 object 权限的权限,但对于请求权限,它们是 除了类范围的权限,因为在调用请求方法之前总是检查这些权限。如果您只想指定每个方法的所有权限,请在类上设置permission_classes = []
。
示例用例:
from rest_framework import views, permissions
class MyView(views.APIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,) # used for default APIView endpoints
queryset = MyModel.objects.all()
serializer_class = MySerializer
@method_permission_classes((permissions.IsOwnerOfObject,)) # in addition to IsAuthenticatedOrReadOnly
def delete(self, request, id):
instance = self.get_object() # ...
希望这可以帮助遇到同样问题的人!
【讨论】:
【参考方案4】:我遇到了这个问题,很想用@permission_classes
装饰器来标记一些具有特定权限的自定义视图方法。我最终想出了一个mixin:
class PermissionsPerMethodMixin(object):
def get_permissions(self):
"""
Allows overriding default permissions with @permission_classes
"""
view = getattr(self, self.action)
if hasattr(view, 'permission_classes'):
return [permission_class() for permission_class in view.permission_classes]
return super().get_permissions()
一个示例用例:
from rest_framework.decorators import action, permission_classes # other imports elided
class MyViewset(PermissionsPerMethodMixin, viewsets.ModelViewSet):
permission_classes = (IsAuthenticatedOrReadOnly,) # used for default ViewSet endpoints
queryset = MyModel.objects.all()
serializer_class = MySerializer
@action(detail=False, methods=['get'])
@permission_classes((IsAuthenticated,)) # overrides IsAuthenticatedOrReadOnly
def search(self, request):
return do_search(request) # ...
【讨论】:
【参考方案5】:这个问题是关于APIView
实例的,但对于任何登陆这里的人来说,使用ViewSets
中的@action
装饰器寻找每个方法的权限覆盖:
class SandwichViewSet(ModelViewSet):
permission_classes = [IsAuthenticated]
@action(..., permission_classes=[CanSeeIngredients])
def retrieve__ingredients(self, request):
...
【讨论】:
这行得通吗?我无法让它工作,它只是忽略了。我进行了调试,它确实覆盖了权限类,但我的端点仍然可以被另一个用户访问。我应该以某种方式调用 super 还是其他什么? @亚当 好的,它确实有效。你需要调用 self.get_object() 来触发权限。【参考方案6】:我遇到了类似的问题。
我想允许未经身份验证的 POST,但不允许未经身份验证的 GET。
未经身份验证的公众成员可以提交项目,但只有经过身份验证的管理员用户才能检索提交的项目列表。
所以我为 POST 构建了一个自定义权限类 - UnauthenticatedPost
,然后将权限类列表设置为 IsAuthentictaed
或 UnauthenticatedPost
。
请注意,我只允许通过使用http_method_names = ['get', 'post']
设置允许的方法来获取和发布。
from django.http import HttpResponse
from rest_framework import viewsets
from rest_framework.permissions import BasePermission, IsAuthenticated
from MyAPI.serializers import MyAPISerializer
from MyAPI.models import MyAPI
class UnauthenticatedPost(BasePermission):
def has_permission(self, request, view):
return request.method in ['POST']
class MyAPIViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated|UnauthenticatedPost]
queryset = MyAPI.objects.all().order_by('-TimeSubmitted')
serializer_class = MyAPISerializer
http_method_names = ['get', 'post']
【讨论】:
【参考方案7】:如果您使用ViewSet
s 或ModelViewSet
s,我认为覆盖get_permissions
就可以了。
看看djoser 是如何处理这个问题的。
例子:
class UserViewSet(viewsets.ModelViewSet):
permission_classes = settings.PERMISSIONS.user # default
def get_permissions(self):
if self.action == "activation": # per action
self.permission_classes = settings.PERMISSIONS.activation
return super().get_permissions()
@action(["post"], detail=False) # action
def activation(self, request, *args, **kwargs):
pass
【讨论】:
【参考方案8】:在 GET、PUT 和 POST 的不同权限方面,我们遇到了同样的挑战,我们使用自定义权限类解决了这个问题:
from rest_framework import permissions
class HasRequiredPermissionForMethod(permissions.BasePermission):
get_permission_required = None
put_permission_required = None
post_permission_required = None
def has_permission(self, request, view):
permission_required_name = f'request.method.lower()_permission_required'
if not request.user.is_authenticated:
return False
if not hasattr(view, permission_required_name):
view_name = view.__class__.__name__
self.message = f'IMPLEMENTATION ERROR: Please add the permission_required_name variable in the API view class: view_name.'
return False
permission_required = getattr(view, permission_required_name)
if not request.user.has_perm(permission_required):
self.message = f'Access denied. You need the permission_required permission to access this service with request.method.'
return False
return True
我们在 API 中这样使用它:
class MyAPIView(APIView):
permission_classes = [HasRequiredPermissionForMethod]
get_permission_required = 'permission_to_read_this'
put_permission_required = 'permission_to_update_this'
post_permission_required = 'permission_to_create_this'
def get(self, request):
# impl get
def put(self, request):
# impl put
def post(self, request):
# impl post
【讨论】:
以上是关于Django REST Framework - 每个方法的单独权限的主要内容,如果未能解决你的问题,请参考以下文章
Django-rest-framework 和 django-rest-framework-jwt APIViews and validation Authorization headers
Django Rest Framework 和 django Rest Framework simplejwt 两因素身份验证