Django Rest Framework JWT 中的 ValidationError 不使用自定义异常处理程序

Posted

技术标签:

【中文标题】Django Rest Framework JWT 中的 ValidationError 不使用自定义异常处理程序【英文标题】:ValidationError in Django Rest Framework JWT does not use custom exception handler 【发布时间】:2015-12-19 05:06:45 【问题描述】:

我正在使用 Django Rest Framework 3.2.3 (DRF) 和 Django Rest Framework JWT 1.7.2(DRF-JWT,https://github.com/GetBlimp/django-rest-framework-jwt)来创建登录令牌。

在发出 JWT 从 400 到 202 时,我需要更改无效凭据的状态代码(仅供参考:我的客户无法读取非 200 响应的正文)。我使用 Django Rest Framework 描述的自定义异常处理程序来实现它:http://www.django-rest-framework.org/api-guide/exceptions/#custom-exception-handling

restapi/custom_exception.py

from rest_framework.views import exception_handler
from rest_framework import status

def custom_exception_handler(exc, context):
    # Call REST framework's default exception handler first,
    # to get the standard error response.
    response = exception_handler(exc, context)

    print ('Exception raised: ' + str(response.status_code))

    # Now add the HTTP status code to the response.
    if response is not None:
        if response.status_code != status.HTTP_403_FORBIDDEN:
            response.status_code = status.HTTP_202_ACCEPTED

    return response

在配置中:

'EXCEPTION_HANDLER': 'restapi.custom_exception.custom_exception_handler',

当使用无效凭据时,DRF-JWT 应该引发 ValidationError。在将无效凭据发布到 JWT 令牌认证接口时,我仍然收到 400 Bad Request 响应代码。

对于所有其他 DRF 接口,我都按预期获得了 202 状态代码。

如何让 DRF-JWT 为其 ValidationErrors 使用自定义异常处理程序?

【问题讨论】:

如果在视图中使用raise ValidationError(msg),则执行自定义异常处理程序。如果在序列化程序中使用raise ValidationError(msg),则使用自定义异常处理程序。有没有办法以某种方式扩展 DRF-JWT? 【参考方案1】:

为什么我们不能在这里使用自定义异常处理程序?

发生这种情况是因为raise_exception 标志在调用.is_valid() 时尚未传递给JSONWebTokenSerializer。 (JSONWebTokenSerializer 是用于验证用户名和密码的序列化程序类。)

post() 方法的 DRF-JWT 源代码:

def post(self, request):
    serializer = self.get_serializer(
        data=get_request_data(request)
    )

    if serializer.is_valid(): # 'raise_exception' flag has not been passed here
        user = serializer.object.get('user') or request.user
        token = serializer.object.get('token')
        response_data = jwt_response_payload_handler(token, user, request)

        return Response(response_data)

    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

现在,我们可以看到raise_exception 标志尚未传递给is_valid()。发生这种情况时,ValidationError 不会引发,从而导致您的 custom_exception_handler 代码无法执行。

根据Raising an exception on invalid data: 上的 DRF 部分

.is_valid() 方法采用可选的 raise_exception 标志 将导致它引发serializers.ValidationError异常如果 存在验证错误。

这些异常由默认异常自动处理 REST framework 提供的处理程序,并将返回 HTTP 400 Bad 默认请求响应。

解决方案:

如果在调用.is_valid()函数时将raise_exception标志传递为True,则custom_exception_handler的代码将被执行。

您需要创建一个CustomObtainJSONWebToken 视图,该视图将从默认的ObtainJSONWebToken 视图继承。在此,我们将重写 .post() 方法以传递 raise_exception 标志。然后将在我们的 url 中指定这个视图。

my_app/views.py

from rest_framework_jwt.views import ObtainJSONWebToken

class CustomObtainJSONWebToken(ObtainJSONWebToken):

    def post(self, request):
        serializer = self.get_serializer(
            data=get_request_data(request)
        )

        serializer.is_valid(raise_exception=True) # pass the 'raise_exception' flag
        user = serializer.object.get('user') or request.user
        token = serializer.object.get('token')
        response_data = jwt_response_payload_handler(token, user, request)
        return Response(response_data)

urls.py

# use this view to obtain token
url(r'^api-token-auth/', CustomObtainJSONWebToken.as_view()) 

【讨论】:

我发现文档中的这句话具有误导性,我的印象是,只有当我直接使用 HTTP_400_BAD_REQUEST 修改响应时,才不会使用异常处理程序。无论如何,如何使用异常处理程序处理 DRF-JWT 中的 400 错误? 我什至不认为,这很合适。如果我在自定义视图raise ValidationError('Invalid data.') 中引发 ValidationError,我仍然会得到预期的 202 状态代码。尽管如果在 JWT (github.com/GetBlimp/django-rest-framework-jwt/blob/master/…) 中引发了 ValidationError,它不会返回 202。 发生这种情况是因为raise_exception 标志在调用.is_valid() 时没有传递给JSONWebTokenSerializer。如果我们传递此参数,则将使用自定义异常处理程序。更新了答案。 非常感谢,这很有魅力。我只需要添加在post() 中使用的from rest_framework_jwt.compat import get_request_datafrom rest_framework_jwt.settings import api_settings【参考方案2】:

我认为也许一种不涉及覆盖 post 方法的更清洁的方法是

serializers.py

from rest_framework_jwt import serializers as jwt_serializers

class JSONWebTokenSerializer(jwt_serializers.JSONWebTokenSerializer):
    """
    Override rest_framework_jwt's ObtainJSONWebToken serializer to 
    force it to raise ValidationError exception if validation fails.
    """
    def is_valid(self, raise_exception=None):
        """
        If raise_exception is unset, set it to True by default
        """
        return super().is_valid(
            raise_exception=raise_exception if raise_exception is not 
None else True)

views.py

from rest_framework_jwt import views as jwt_views
from .serializers import JSONWebTokenSerializer

class ObtainJSONWebToken(jwt_views.ObtainJSONWebToken):
    """
    Override the default JWT ObtainJSONWebToken view to use the custom serializer
    """
    serializer_class = JSONWebTokenSerializer

urls.py

from django.conf.urls import url
from .views import ObtainJSONWebToken

urlpatterns = [
    ...
    url(r'^api-token-auth/', ObtainJSONWebToken.as_view(), name='jwt-create'),
]

【讨论】:

以上是关于Django Rest Framework JWT 中的 ValidationError 不使用自定义异常处理程序的主要内容,如果未能解决你的问题,请参考以下文章

DRF:如何将 django-rest-framework-jwt 集成到 Djoser

Django Rest Framework 中的 JWT 身份验证错误“无效签名”

Django Rest Framework JWT 身份验证测试

Django-Rest-Framework JWT 单元测试说“未提供身份验证”

Django Rest Framework 避免身份验证 JWT

如何让 django-rest-framework-jwt 在注册时返回令牌?