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

ValidationError in Django Rest Framework JWT does not use custom exception handler

我正在使用 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_exception 标志在调用 .is_valid() 时没有传递给 JSONWebTokenSerializer。 (JSONWebTokenSerializer 是用于验证用户名和密码的序列化程序 class。)

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 部分

The .is_valid() method takes an optional raise_exception flag that will cause it to raise a serializers.ValidationError exception if there are validation errors.

These exceptions are automatically dealt with by the default exception handler that REST framework provides, and will return HTTP 400 Bad Request responses by default.

解决方案:

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

您需要创建一个 CustomObtainJSONWebToken 视图,它将继承默认的 ObtainJSONWebToken 视图。在此,我们将覆盖 .post() 方法以传递 raise_exception 标志。然后将在我们的网址中指定此视图。

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()) 

我认为不涉及覆盖 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'),
]