Django Rest Framework-带有身份验证/权限装饰器的GenericViewSet

时间:2019-11-17 20:02:30

标签: django authentication django-rest-framework permissions django-generic-views

目前,我为单个实体设置了简单的休息设置,

您可以创建一个对象,并可以通过ID对其进行检索。

“ POST”需要身份验证/权限,“ RETRIEVE”不需要身份验证/权限。

settings.py(需要每个资源的全局身份验证/权限):

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'api.authentication.token_authentication.TokenAuthentication'
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated'
    ]
}

在我的资源中,正确地应用了全局身份验证/权限设置,但我想为检索方法生成一个例外:

my-resource.py:

from django.utils.translation import gettext_lazy as _
from rest_framework import mixins, serializers, viewsets
from rest_framework.decorators import authentication_classes, permission_classes

from api.models import Entity


class EntitySerializer(serializers.ModelSerializer):

    class Meta:
        model = Entity
        fields = [...]
        read_only_fields = [...]


class EntityViewSet(
    mixins.CreateModelMixin,
    mixins.RetrieveModelMixin,
    viewsets.GenericViewSet
):

    queryset = Entity.objects.all()
    serializer_class = EntitySerializer

    @permission_classes([]) # this is ignored ?
    @authentication_classes([]) # this is ignored too ?
    def retrieve(self, request, *args, **kwargs):
        return super().retrieve(request, *args, **kwargs)

结果:

  1. “ POST”按预期工作
  2. “ RETRIEVE”返回403?

为什么检索方法仍然需要验证并返回403?

有没有更简单的方法来做到这一点?

问候和感谢!

4 个答案:

答案 0 :(得分:4)

选项1。

将装饰器更改为class属性,只需将Permissions_classes设置为IsAuthenticatedOrReadOnlyIsAuthenticatedOrReadOnly将允许经过身份验证的用户执行任何请求。仅当请求方法是“安全”方法之一时,才允许对未授权用户的请求; GET,HEAD或OPTIONS。

from rest_framework.permissions import IsAuthenticatedOrReadOnly

class EntityViewSet(
    mixins.CreateModelMixin,
    mixins.RetrieveModelMixin,
    viewsets.GenericViewSet
):

    queryset = Entity.objects.all()
    serializer_class = EntitySerializer
    permission_classes = (IsAuthenticatedOrReadOnly,)

选项2。

创建自己的permission并根据您的需求进行调整。然后,将其设置为类属性。

示例permissions.py

from rest_framework.permissions import BasePermission

class IsStaff(BasePermission):
    def has_permission(self, request, view):
        return request.user.is_authenticated and request.user.is_staff

您的文件:

from .permissions import IsStaff
class EntityViewSet(
    mixins.CreateModelMixin,
    mixins.RetrieveModelMixin,
    viewsets.GenericViewSet
):

    queryset = Entity.objects.all()
    serializer_class = EntitySerializer
    permission_classes = (IsStaff,)

选项3。

覆盖类中的get_permissions方法。

from rest_framework.permissions import AllowAny

def get_permissions(self):
    if self.action == 'retrieve':
        return [AllowAny]
    return [IsAuthenticated]

选项4。

如果您没有为retrieve方法指定任何权限,则Django Rest Framework将应用您在settings.py中指定的默认权限,本例中为{{1} }。对于IsAuthenticated方法,您应该做的是允许任何用户使用。

retrieve

请注意,当您通过class属性或装饰器设置新的权限类别时,您是在告诉视图忽略在from rest_framework.permissions import AllowAny @permission_classes([AllowAny]) def retrieve(self, request, *args, **kwargs): return super().retrieve(request, *args, **kwargs) 文件上设置的默认列表。

不必太担心settings.py,因为authentication_classes权限类将允许不受限制的访问,而不管请求是经过身份验证还是未经身份验证。

答案 1 :(得分:2)

我的建议是更改设置文件中的默认权限类别

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'api.authentication.token_authentication.TokenAuthentication'
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticatedOrReadOnly'
    ]
}

现在,您不必在视图上应用权限类。

答案 2 :(得分:1)

如果您使用的是 基于类的DRF视图 ,请使用覆盖 get_permissions 方法来正确设置权限, 例如,


from rest_framework.permissions import AllowAny, IsAdminUser, IsAuthenticated


class MyViewSet(...):
    def get_permissions(self):
        if self.action == 'retrieve':
            retrieve_permission_list = [AllowAny, IsAdminUser, ]
            return [permission() for permission in retrieve_permission_list]
        elif self.action == 'create':
            create_permission_list = [IsAuthenticated, IsAdminUser]
            return [permission() for permission in create_permission_list]
        else:
            return super().get_permissions()

以您为例,

class EntityViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    ...

    def get_permissions(self):
        if self.action == 'retrieve':
            return []
        else:
            return super().get_permissions()

答案 3 :(得分:1)

权限检查实际上是通过check_permissions方法完成的,该方法调用通过get_permissions方法检索的每个定义的权限。

因此,最简单,最简单的方法是覆盖check_permissions并根据请求的操作(基于方法-操作图)在其中添加您的权限逻辑。将以下内容添加到您的EntityViewSet类中:

def check_permissions(self, request):
    if self.action == 'retrieve':
        return
    super().check_permissions(request)

因此,当actionretrieve时,请勿进行权限检查,否则请照常进行操作。

FWIW,check_permissions实际上是在views.APIViewgenerics.GenericAPIView的超类)中定义的。


为什么忽略您的authentication_classes / permission_classes装饰器?

authentication_classes / permission_classes装饰器设计用于基于功能的视图,例如由api_view装饰器创建。

permission_classes装饰器在装饰后的函数上设置permission_classes属性:

def permission_classes(permission_classes):
    def decorator(func):
        func.permission_classes = permission_classes
        return func
    return decorator

在您的情况下,这是retrieve方法,但是您需要在(要创建的)实例或类(EntityViewSet)上设置属性。因此,您的permission_classes的权限 retrieve方法上的装饰器没有任何影响。 authentication_classes装饰器也以相同的方式工作,因此也不起作用。

FWIW,api_view实际上是动态创建views.APIView类的子类,并将permission_classesauthentication_classes属性设置为修饰后的子类功能 1 。因此,在那种情况下,修饰的类使用authentication_classes / permission_classes修饰符是有意义的,因为这些属性最终将按原样应用。

1 如果修饰的函数不具有属性(例如),则使用默认设置。相关装饰器未应用。