'WSGIRequest'对象没有属性'successful_authenticator'

时间:2013-05-24 13:55:51

标签: django authentication token django-rest-framework

我已经创建了一个认证类:

Token Authentication for RESTful API: should the token be periodically changed?

restapi/settings.py

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        # 'rest_framework.authentication.TokenAuthentication',
        'restapi.authentication.ExpiringTokenAuthentication',
    ),
    'PAGINATE_BY': 10
}

restapi/authentication.py

import datetime
from rest_framework.authentication import TokenAuthentication

class ExpiringTokenAuthentication(TokenAuthentication):
    def authenticate_credentials(self, key):
        try:
            token = self.model.objects.get(key=key)
        except self.model.DoesNotExist:
            raise exceptions.AuthenticationFailed('Invalid token')

        if not token.user.is_active:
            raise exceptions.AuthenticationFailed('User inactive or deleted')

        # This is required for the time comparison
        utc_now = datetime.utcnow()
        utc_now = utc_now.replace(tzinfo=pytz.utc)

        if token.created < utc_now - timedelta(hours=24):
            raise exceptions.AuthenticationFailed('Token has expired')

        return token.user, token

restapi/tests.py

def test_get_missions(self):
    """
    Tests that /missions/ returns with no error
    """
    response = self.client.get('/missions/', HTTP_AUTHORIZATION = self.auth)

在我的测试中,我有一个例外AttributeError: 'WSGIRequest' object has no attribute 'successful_authenticator'

为什么我会出现此错误?如何解决?

2 个答案:

答案 0 :(得分:6)

问题来自这条线:

utc_now = datetime.utcnow()

导致AttributeError: 'WSGIRequest' object has no attribute 'successful_authenticator'

已经有一段时间了,因为我偶然发现了这样一个误导性的错误信息。

以下是我解决它的方法:

restapi/authentication.py

import datetime
from django.utils.timezone import utc
from rest_framework.authentication import TokenAuthentication
from rest_framework import exceptions

class ExpiringTokenAuthentication(TokenAuthentication):
    def authenticate_credentials(self, key):
        try:
            token = self.model.objects.get(key=key)
        except self.model.DoesNotExist:
            raise exceptions.AuthenticationFailed('Invalid token')

        if not token.user.is_active:
            raise exceptions.AuthenticationFailed('User inactive or deleted')

        utc_now = datetime.datetime.utcnow().replace(tzinfo=utc)

        if token.created < utc_now - datetime.timedelta(hours=24):
            raise exceptions.AuthenticationFailed('Token has expired')

        return (token.user, token)

答案 1 :(得分:2)

对我来说,这与自定义身份验证类无关,但与处理dispatch()函数中定义的方法调用的方式无关。例如,我有以下设置:

class MyView(GenericAPIView):
    queryset = MyModel.objects.all()
    lookup_field = 'my_field'

    def dispatch(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super().dispatch(request, *args, **kwargs)

self.get_object函数调用了我感兴趣的两件事。主要是self.check_object_permissions函数和get_object_or_404函数。对我来说,如果返回check_object_permissions,则在False函数中出错。这将导致对self.permission_denied()的调用,然后将尝试访问请求上的successful_authenticator属性。但是因为请求未设置为rest_framework.request.Request,所以该属性将不存在。这是因为dispatch方法会在进行过程中设置正确的请求。由于此操作是在{em>之后之后进行的,因此self.get_object()调用永远不会正确设置。

我个人感到非常惊讶,因为dispatch()函数通常用于确定某些关键情况是否正确(即对象是否存在以及用户是否具有权限)。如果还可以,我们将继续处理请求。但是,显然没有办法用这种观点来做到这一点。我唯一可以解决的方法是将self.get_object()调用移至我的get()方法,如下所示:

class MyView(GenericAPIView):
    queryset = MyModel.objects.all()
    lookup_field = 'my_field'

    def get(self, request, *args, **kwargs):
        my_object = self.get_object()

我还尝试过在self.check_object_permissions函数中手动调用get_object_or_404dispatch(),但无济于事。一个要求另一个(权限检查需要对象,要获得该对象,您必须执行404检查)。尝试在权限和404检查之前调用super意味着get()函数将首先被调用,基本上消除了您最初尝试在dispatch函数中获得的效果。据我所知,这是解决这种情况的唯一方法。

还可以在django-rest-framework上看到此问题,以获取一些有趣的信息:https://github.com/encode/django-rest-framework/issues/918