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_data
和from 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 单元测试说“未提供身份验证”