Django REST Framework——6. 认证、权限、限流、过滤、排序及异常处理

身份认证是将传入的请求与一组标识凭据(例如发送该请求的用户或其签名的token)相关联的机制。然后,”权限”和”限流”可以使用这些凭据来确定是否应允许该请求。

DRF提供了几种开箱即用的身份认证方案,还允许我们自定义认证方案。

两个与认证相关的request属性:

  • request.user属性通常被设置为 contrib.auth包中 User类的一个实例;
  • request.auth属性用于任何其他身份认证信息,例如,它可以用于表示请求签名的身份认证令牌(token)。

1.1 身份认证的流程

身份认证总是在视图的最开始运行,在执行视图其他任何其他代码之前执行。

身份认证方案被定义为一个列表,DRF将尝试使用列表中的每个类进行身份认证,并使用第一个成功身份认证的类的返回值设置 request.userrequest.auth

如果没有类进行认证, request.user将被设置成 django.contrib.auth.models.AnonymousUser(django的匿名用户)的实例, request.auth将被设置成 None

对于未经身份认证的请求,可以使用 UNAUTHENTICATED_USERUNAUTHENTICATED_TOKEN设置修改 request.userrequest.auth的值。

1.2 自定义认证类

要实现自定义的认证类,就要继承 BaseAuthentication 类并且重写 authenticate(self, request) 方法。如果认证成功,该方法应返回 (user, auth) 的二元元组,否则返回None。

在某些情况下,也可以不返回None,而是抛出 AuthenticationFailed 异常:

  • 如果不尝试认证而返回None,则将继续检查其他任何正在使用的身份认证方案。
  • 如果试图进行身份认证但失败,则应该抛出 AuthenticationFailed异常。这将立即返回一个错误响应,无论是否进行权限检查,也不继续检查其他任何身份认证方案。

通常情况下,我们会在APP文件夹下创建一个py文件,单独存放这些认证类:

from app01 import models
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed

class authlogin(BaseAuthentication):
    def authenticate(self, request):
        token = request.GET.get('token')
        res = models.UserToken.objects.filter(token = token).first()
        if res:
            return (res.user,token)
        else:
            raise AuthenticationFailed('您没有登录')

1.3 设置认证方案

  • 局部使用: 在视图中设置以下属性,则只会对该视图生效:
class MyView(APIView):
    authentication_classes = [认证类, 认证类......]
    ......

注意:如果列表为空,则说明对视图不进行认证限制,相当于局部禁用认证。
* 全局使用: 在项目配置文件settings.py中,写入以下内容,则会对全部视图生效:

REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES": [
            "app名称.自建py文件.自定义认证类",
            ......

        ]
}

注意:如果配置多个认证类,则要把返回元组的类放在最后。

确定好用户的身份后,就需要确定具体的用户权限了。权限用来确定用户是否可以对API进行访问,DRF自带了一些权限,并且支持我们自定义权限。

2.1 权限检查流程

权限检查始终在视图的身份认证代码之后,其他任何代码之前执行。

与系统认证方案类似,权限也是通过一个列表进行设置,列表中包含的也是一个个的类。

在运行视图主体之前,将检查列表中的每个权限。如果任何权限检查失败,将抛出 exceptions.PermissionDeniedexceptions.NotAuthenticated异常,并且视图的主体将不会运行。

2.2 对象级别的权限

上面说的是视图级别权限,DRF也支持对象级权限。对象级权限用于确定是否应该允许用户对特定对象进行操作,该对象通常是一个模型实例。

当调用 get_object()方法时,对象级权限由DRF的通用视图(GenericAPIView及其子类)运行。如果不允许用户对给定对象进行操作,则会引发 PermissionDenied异常。

如果想强制执行对象级别的权限检测,或者像重写 get_object()方法。那么需要在检索对象的时候显式调用 check_object_permissions(request, obj)方法。这将抛出 PermissionDeniedNotAuthenticated异常,或者如果视图有适当的权限就直接返回对象。

比如:

def get_object(self):
    obj = get_object_or_404(self.get_queryset(), pk=self.kwargs["pk"])
    self.check_object_permissions(self.request, obj)
    return obj

对象级别权限的限制:
出于性能原因,通用视图在返回对象列表时不会将对象级权限应用于查询集中的每个实例。
所以在通常情况下,当我们使用对象级权限时,还需要适当地过滤查询集,以确保用户只能看到他们被允许查看的实例。

2.3 自定义权限

要实现自定义权限,就要 写一个类并继承 BasePermission 类,然后重写以下方法中的两个或一个

  • has_permission(self, request, view):用于视图级别权限。
  • has_object_permission(self, request, view, obj:用于对象级别权限。

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

如果测试失败,自定义权限将抛出一个 PermissionDenied 异常。若要更改与异常关联的错误消息,请在自定义权限上直接实现 message属性。否则,将使用 PermissionDenieddefault_detail属性。

from rest_framework import permissions

class CustomerPermission(permissions.BasePermission):
    message = '没有权限!'

    def has_permission(self, request, view):
         ...

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

if request.method in permissions.SAFE_METHODS:

else:

注意: 仅当视图级 has_permission检查已通过时,才会调用对象级 has_object_permission方法。
另外,为了运行对象级别检查,视图代码应显式调用 check_object_permissions(request, obj)。但如果使用的是通用视图(GenericAPIView及其子类),那么默认会替我们自动处理。

2.4 设置权限

  • 局部使用: 在视图中设置以下属性,则只会对该视图生效:
class MyView(APIView):
    ......

    permission_classes = [权限类, 权限类......]
    ......

注意:如果列表为空,则说明对视图不进行认证限制,相当于局部禁用认证。
* 全局使用:
* 在项目配置文件settings.py中,写入以下内容,则会对全部视图生效:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        "app名称.自建py文件.自定义认证类",
        ......

    ]
}

限流类似于权限,它决定是否应该授权请求,二者配合使用,用于控制客户端可以向API发出的请求速率。 用法也与权限极为相似。

3.1 限流检查流程

与权限和身份验证一样,DRF中的限流总是定义为一个列表,内部包含若干个类。

在运行视图主体之前,会检查列表中的每个限流。如果任何限流检查失败,则抛出 exceptions.Throttled异常,并且视图的主体将不会运行。

3.2 内置限流

AnonRateThrottle只会限制未经认证的用户(匿名用户或者未登录用户)。传入请求的IP地址用于生成一个惟一的密钥,以此来限流。

UserRateThrottle将限制用户在API中给定的请求速率。用户id用于生成一个唯一的键,以此来限流。未经身份验证的请求则会像 AnonRateThrottle那样,使用IP地址用于生成一个惟一的密钥,以此来限流。

3.3 设置限流

  • 设置速率: 无论是局部使用还是全局使用,都要在项目配置文件settings.py中设置速率:
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/day',
        'user': '1000/day'
    }
}

DEFAULT_THROTTLE_RATES 可以使用 secondminutehourday 作为限流的周期
* 局部使用: 在视图中设置以下属性,则只会对该视图生效:

class MyView(APIView):
    ......

    throttle_classes = [UserRateThrottle,......]
    ......

注意:如果列表为空,则说明对视图不进行认证限制,相当于局部禁用认证。
* 全局使用: 在项目配置文件settings.py中写入以下内容:

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle'
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/day',
        'user': '1000/day'
    }
}

DRF的通用列表视图(generic list views)的默认行为是返回模型的整个查询集。通常,我们希望API限制queryset返回的项,此时过滤就派上用场了。

对于 GenericAPIView 的子视图来说,最简单的过滤任意查询集的方法就是重写它的 get_queryset() 方法

4.1 根据当前用户进行过滤

如果我们想要过滤queryset,确保只返回与当前发出请求的已验证用户相关的结果。那么就可以通过基于 request.user的值进行过滤来实现。

from myapp.models import Purchase
from myapp.serializers import PurchaseSerializer
from rest_framework import generics

class PurchaseList(generics.ListAPIView):
    serializer_class = PurchaseSerializer

    def get_queryset(self):
"""
        这个视图只返回当前认证用户的采购列表。
"""
        user = self.request.user
        return Purchase.objects.filter(purchaser=user)

4.2 根据URL进行过滤

在url中捕获参数:

path('purchases//', PurchaseList.as_view())

然后在视图中根据捕获的参数进行过滤:

class PurchaseList(generics.ListAPIView):
    serializer_class = PurchaseSerializer

    def get_queryset(self):
"""
        这个视图只返回由URL的username部分所指定用户的采购列表。
"""
        username = self.kwargs['username']
        return Purchase.objects.filter(purchaser__username=username)

4.3 使用第三方django-filter库

django-filter库包含了一个 DjangoFilterBackend类,它支持DRF的高度定制字段过滤,需要我们自己安装:

pip install django-filter

注册到django项目的 INSTALLED_APPS

INSTALLED_APPS = [
    ...

    'django_filters',
    ...

]

全局设置,对所有视图都有效:

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
}

局部设置,只对当前视图有效:

from django_filters.rest_framework import DjangoFilterBackend

class UserListView(generics.ListAPIView):
    ...

    filter_backends = [DjangoFilterBackend]

如果只是需要简单的通过字段值是否相等进行过滤,那么就可以在视图或视图集上设置一个 filterset_fields属性,列出您希望过滤的字段集。

class ProductList(generics.ListAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['category', 'in_stock']

这将自动为给定的字段创建一个FilterSet类,并允许使用url的查询集字符串进行过滤:

http://example.com/api/products?category=clothing&in_stock=True

OrderingFilter类支持简单的查询参数控制的结果排序。默认情况下,查询参数名为 ordering。比如:

http://example.com/api/users?ordering=username

如果想要降序,在字段名前加上 -

http://example.com/api/users?ordering=-username

还可以指定多个字段:

http://example.com/api/users?ordering=account,username

5.1 指定允许作为排序依据的字段

可以通过在视图中设置 ordering_fields属性,指定允许作为排序依据的所有字段,防止敏感信息泄露:

class UserListView(generics.ListAPIView):
    ......

    filter_backends = [filters.OrderingFilter]
    ordering_fields = ['username', 'email']

如果肯定没有敏感信息,可以使用 '__all__'指定所有字段。

class BookingsListView(generics.ListAPIView):
    ......

    filter_backends = [filters.OrderingFilter]
    ordering_fields = '__all__'

5.2 指定默认排序字段

ordering属性可以是一个字符串,也可以是字符串构成的列表或元组:

class UserListView(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    filter_backends = [filters.OrderingFilter]
    ordering_fields = ['username', 'email']
    ordering = ['username']

同样的,需要降序可以加 -

5.3 修改查询参数名称

url中查询参数的默认名称为 ordering,我们可以通过 ORDERING_PARAM设置覆盖:

REST_FRAMEWORK = {
    'ORDERING_PARAM': 'order_by',
}

DRF的视图能够处理各种异常,并返回适当的错误响应。这样能有助于我们与前端开发人员合作,统一接口,更加合理的处理这些异常。

响应内容包括状态码、内容类型和响应主体,响应主体则包含了错误更加详细的信息。比如, DELETE方法不被允许的错误响应:

HTTP/1.1 405 Method Not Allowed
Content-Type: application/json
Content-Length: 42

{"detail": "Method 'DELETE' not allowed."}

而验证错误的处理方式略有不同,它会将验证失败的字段作为键。比如,下面 amountdescription字段的验证错误:

HTTP/1.1 400 Bad Request
Content-Type: application/json
Content-Length: 94

{"amount": ["A valid integer is required."], "description": ["This field may not be blank."]}

如果验证错误不是关于某个特定字段的(也许涉及多个字段),那么就会以 non_field_errors 作为键。想要修改就要在项目配置中设置 NON_FIELD_ERRORS_KEY的值。

6.1 自定义异常处理器

我们可以通过创建一个处理函数来实现自定义异常处理,该处理函数将API视图中引发的异常转换为响应对象,以便我们控制错误响应的格式。

该函数必须接受两个参数:

异常处理函数要么返回一个 Response对象,要么返回 None(如果无法处理异常)。如果处理程序返回 None,那么这个异常将被重新抛出,Django将返回一个标准的 HTTP 500 ‘server error’ 响应。

from rest_framework.views import exception_handler

def custom_exception_handler(exc, context):

    response = exception_handler(exc, context)

"""
    按照我们自己的规范,定制错误响应
    这里我们在标准错误响应的基础上添加了一个状态码
"""
    if response is not None:
        response.data['status_code'] = response.status_code

    return response

6.2 配置异常处理器

自定义的异常处理器需要进行配置,否则不会生效。

在项目配置文件中:

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
}

如果不配置,则会使用默认的异常处理器:

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
}

Original: https://blog.csdn.net/qq_39330486/article/details/122070423
Author: 花_城
Title: Django REST Framework——6. 认证、权限、限流、过滤、排序及异常处理

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/736427/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球