手摸手,带你用Django REST Framework撸接口系列七(权限篇)

身份验证或身份识别本身通常不足以获取信息或代码的访问权限。因此,请求访问的实体必须具有授权;权限控制可以限制用户对于视图的访问和对于具体数据对象的访问。

权限被执行的时机:

  • 在执行视图的dispatch()方法前,会先进行视图访问权限的判断
  • 在通过get_object()获取具体对象时,会进行对象访问权限的判断

认证与权限的区别

认证(Authentication)与权限(Permission)不是一回事。认证是通过用户提供的用户ID/密码组合或者Token来验证用户的身份。权限(Permission)的校验发生验证用户身份以后,是由系统根据分配权限确定用户可以访问何种资源以及对这种资源进行何种操作,这个过程也被称为授权(Authorization)。

无论是Django还是DRF, 当用户成功通过身份验证以后,系统会把已通过验证的用户对象与request请求绑定,这样一来你就可以使用request.user获取这个用户对象的所有信息了。

DRF自带权限类

AllowAny

AllowAny权限类将允许不受限制的访问,而不管该请求是否已通过身份验证或未经身份验证

此权限不是严格要求的,因为你可以通过使用空列表或元组进行权限设置来获得相同的结果,但你可能会发现指定此类很有用,因为它使意图更明确。

IsAuthenticated

IsAuthenticated 权限类将拒绝任何未经身份验证的用户的权限,并允许其他权限。 如果你希望你的API仅供注册用户访问,则此权限适用。

如果你希望你的API允许匿名用户读取权限,并且只允许对已通过身份验证的用户进行写入权限,则此权限是适合的。

IsAdminUser

除非user.is_staffTrue,否则IsAdminUser权限类将拒绝任何用户的权限,在这种情况下将允许权限。

如果你希望你的API只能被部分受信任的管理员访问,则此权限是适合的。

IsAuthenticatedOrReadOnly

IsAuthenticatedOrReadOnly 将允许经过身份验证的用户执行任何请求。只有当请求方法是“安全”方法(GET, HEADOPTIONS)之一时,才允许未经授权的用户请求。

如果你希望你的API允许匿名用户读取权限,并且只允许对已通过身份验证的用户进行写入权限,则此权限是适合的。

DjangoModelPermissions

此权限类与Django的标准django.contrib.authmodel权限相关联。此权限只能应用于具有.queryset属性集的视图。只有在用户通过身份验证并分配了相关模型权限的情况下,才会被授予权限。

  • POST 请求要求用户对模型具有添加权限。
  • PUTPATCH 请求要求用户对模型具有更改权限。
  • DELETE 请求想要求用户对模型具有删除权限。

默认行为也可以被重写以支持自定义模型权限。例如,你可能希望为GET请求包含一个查看模型的权限。

要使用自定义模型权限,请覆盖DjangoModelPermissions并设置.perms_map属性。有关详细信息,请参阅源代码。

使用不包含queryset属性的视图。

如果你在重写了get_queryset()方法的视图中使用此权限,这个视图上可能没有queryset属性。在这种情况下,我们建议还使用保护性的查询集来标记视图,以便此类可以确定所需的权限。比如:

1
queryset = User.objects.none()  # DjangoModelPermissions需要一个queryset

DjangoModelPermissionsOrAnonReadOnly

DjangoModelPermissions类似,但也允许未经身份验证的用户具有对API的只读访问权限。

DjangoObjectPermissions

此权限类与Django的标准对象权限框架相关联,该框架允许模型上的每个对象的权限。为了使用此权限类,你还需要添加支持对象级权限的权限后端,例如django-guardian。

DjangoModelPermissions一样,此权限只能应用于具有.queryset属性或.get_queryset()方法的视图。只有在用户通过身份验证并且具有相关的每个对象权限和相关的模型权限后,才会被授予权限。

  • POST 请求要求用户对模型实例具有添加权限。
  • PUTPATCH 请求要求用户对模型示例具有更改权限。
  • DELETE 请求要求用户对模型示例具有删除权限。

请注意,DjangoObjectPermissions 不需要 django-guardian软件包,并且应当同样支持其他对象级别的后端。

DjangoModelPermissions一样,你可以通过重写DjangoObjectPermissions并设置.perms_map属性来使用自定义模型权限。有关详细信息,请参阅源代码。


注意:如果你需要GET,HEADOPTIONS请求的对象级视图权限,那么你还需要考虑添加DjangoObjectPermissionsFilter类,以确保相应API只返回包含用户具有适当视图权限的对象的结果。

如何设置权限

默认权限策略可以使用DEFAULT_PERMISSION_CLASSES设置进行全局设置。比如:

1
2
3
4
5
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}

如果未指定,则此设置默认为允许无限制访问:

1
2
3
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
)

你还可以使用基于APIView类的视图在每个视图或每个视图集的基础上设置身份验证策略。

1
2
3
4
5
6
7
8
9
10
11
12
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

class ExampleView(APIView):
permission_classes = (IsAuthenticated,)

def get(self, request, format=None):
content = {
'status': 'request was permitted'
}
return Response(content)

或者你可以使用@api_view装饰器装饰基于函数的视图。

1
2
3
4
5
6
7
8
9
10
11
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

@api_view(['GET'])
@permission_classes((IsAuthenticated, ))
def example_view(request, format=None):
content = {
'status': 'request was permitted'
}
return Response(content)

注意: 当你通过类属性或装饰器设置新的权限类时,你要通知视图忽略__settings.py__文件中设置的默认列表。

自定义权限

大多数情况下,默认的权限类不能满足我们的要求,这时就需要自定义权限了。自定义的权限类需要继承BasePermission类并根据需求重写has_permission(self,request,view)has_object_permission(self,request, view, obj)方法。你还可以通过message自定义返回的错误信息。

1
2
3
4
5
6
7
8
9
10
11
12
from rest_framework import permissions

class CustomerPermission(permissions.BasePermission):
# message = 'You have not permissions to do this.'
# 或
message = {"status": False, "error": "自定义的错误提示信息"}
# 视图级权限
def has_permission(self, request, view):
...
# 对象级权限(当视图级权限通过后才会执行对象级权限)
def has_object_permission(self, request, view, obj):
...

如果请求被授予访问权限,方法应该返回True,否则返回False

如果你需要测试请求是读取操作还是写入操作,则应该根据常量SAFE_METHODS检查请求方法,SAFE_METHODS是包含'GET', 'OPTIONS''HEAD'的元组。例如:

1
2
3
4
if request.method in permissions.SAFE_METHODS:
# 检查只读请求的权限
else:
# 检查读取请求的权限

注意: 仅当视图级has_permission检查已通过时,才会调用实例级has_object_permission方法。另请注意,为了运行实例级别检查,视图代码应显式调用.check_object_permissions(request, obj)。如果你使用的是通用视图,那么默认会为你处理。


如果校验失败,自定义权限将引发PermissionDenied异常。要更改与异常关联的错误消息,请直接在自定义权限上实现消息属性。否则将使用PermissionDenieddefault_detail属性。

示例

还是以我们的文章模型为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Article(models.Model):
"""文章模型"""
# 文章状态
STATUS_CHOICES = (
('p', '发布'),
('d', '草稿'),
)

title = models.CharField(verbose_name='标题', max_length=90, db_index=True)
body = models.TextField(verbose_name='内容', blank=True)
author = models.ForeignKey(User, verbose_name='作者', on_delete=models.CASCADE, related_name='articles')
status = models.CharField(verbose_name='状态', max_length=1, choices=STATUS_CHOICES, default='s', null=True, blank=True)
created_time = models.DateTimeField(verbose_name='发布日期', auto_now_add=True)

def __str__(self):
"""返回文章标题"""
return self.title

class Meta:
# 排序,主键倒序
ordering = ['-pk']
verbose_name = "文章"
verbose_name_plural = verbose_name

class ArticleSerializer(serializers.ModelSerializer):
"""文章序列化类"""

class Meta:
model = Article
fields = "__all__"
read_only_fields = ('id', 'created_time', 'author')
# depth = 1

我们只希望发布该文章的用户才能够进行文章修改删除操作。

定义自定义权限类

1
2
3
4
5
6
7
8
9
10
11
12
13
from rest_framework import permissions


class IsOwnerArticleCanHandle(permissions.BasePermission):
"""文章创建者才可以编辑"""

def has_object_permission(self, request, view, obj):
# 非文章创建者可以允许get,head和options请求,直接放行。
if request.method in permissions.SAFE_METHODS:
return True

# 如果文章的作者等于当前用户则可以进行任何操作
return obj.author == request.user

视图类使用该权限

1
2
3
4
5
6
7
8
9
10
class ArticleView(viewsets.ModelViewSet):
"""文章列表视图类"""
queryset = Article.objects.all()
serializer_class = ArticleSerializer
# 文章作者才能修改该文章
permission_classes = (IsOwnerArticleCanHandle, )

def perform_create(self, serializer):
# 将request.user与author绑定。调用create方法时执行如下函数。
serializer.save(author=self.request.user)

此时我们登录用户,访问一个自己发表的文章,可以看到,文章作者可以直接在这个页面进行修改删除操作。

访问非自己发表的文章试试,修改删除项全都不见了

登录注销

以上在我们使用Django REST framework自带响应器时,我们如何切换登录注销呢?

这就需要门将登录页面添加到路由中了

1
2
# 用户登录页面
path('api-auth/', include('rest_framework.urls')),

然后访问api-auth/login/你就可以看到专门的DRF的登录页面了,如下所示:

你也可以这样定义message

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class IsMember(permissions.BasePermission):
# 无权限返回消息
message = ""

def has_permission(self, request, view):
self.message = {"status": False, "error": "你不不是普通管理员"}
if request.user.id < 2:
return False
return True

def has_object_permission(self, request, view, obj):
self.message = {"status": False, "error": "你只能访问id小于10的文章"}
if obj.id > 10:
return False
return True

返回的结果就是:

1
2
3
4
5
6
7
8
9
{
"status": "False",
"error": "你不提示普通管理员"
}
# 或
{
"status": "False",
"error": "你只能访问id小于10的文章"
}

总结

介绍了如何使用Django REST Framework自带的权限类,如何自定义权限类及如何配置权限类实现了对用户访问API资源进行控制。本文中我们使用了默认的基于session机制的用户认证机制,下篇中我们将重点介绍DRF的认证机制,并把重点放在Token认证上。