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

一、认证(Authentication)

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

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

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')  # 来访问数据的时候需要携带的token
        res = models.UserToken.objects.filter(token = token).first()  # 如果和数据库的值不一致,不允许访问数据
        if res:
            return (res.user,token)# 返回元组,第一个值会给request.user ,第二值会给request.auth
        else:
            raise AuthenticationFailed('您没有登录')

1.3 设置认证方案

  • 局部使用:

    在视图中设置以下属性,则只会对该视图生效:

    class MyView(APIView):  # 只对APIView的子类有效
        authentication_classes = [认证类, 认证类……]
        ……
    

    注意:如果列表为空,则说明对视图不进行认证限制,相当于局部禁用认证。

  • 全局使用:

    在项目配置文件settings.py中,写入以下内容,则会对全部视图生效:

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

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

二、权限(Permissions)

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

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):  # 只对APIView的子类有效
        ……
        permission_classes = [权限类, 权限类……]
        ……
    

    注意:如果列表为空,则说明对视图不进行认证限制,相当于局部禁用认证。

  • 全局使用:

  • 在项目配置文件settings.py中,写入以下内容,则会对全部视图生效:

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

三、限流(Throttling)

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

3.1 限流检查流程

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

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

3.2 内置限流

3.2.1 AnonRateThrottle

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

3.2.2 UserRateThrottle

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):  # 只对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'
        }
    }
    

四、过滤(Filtering)

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/<str:username>/', 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库

4.3.1 安装配置

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]

4.3.2 简单使用

如果只是需要简单的通过字段值是否相等进行过滤,那么就可以在视图或视图集上设置一个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

更加详细的说明请参考django-filter的官方文档:传送门

五、排序(Ordering)

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',
}

六、异常(Exceptions)

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视图中引发的异常转换为响应对象,以便我们控制错误响应的格式

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

  1. 第一个是要处理的异常;
  2. 第二个是包含上下文的字典,比如通过context['view']可以获取当前正在处理的视图。

异常处理函数要么返回一个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'
}
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

花_城

你的鼓励就是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值