没有标题的休息框架令牌认证

时间:2014-11-30 17:27:48

标签: django authentication django-rest-framework

我正在尝试使用 Django Rest Framework 来限制列表视图。

如果我使用错误的令牌发送请求:

Authorization: Token f1cfedb0895105ee088e8aab5bf0ae7ca9752e79

它返回 HTTP 401 错误,这是正确的

但是,如果我只是从HTTP标头中删除授权,它将在不进行身份验证的情况下进行查询并返回 HTTP 200

我在这里做错了什么:

class OrderViewSet(viewsets.ModelViewSet):
    serializer_class = OrderSerializer
    model = Order


    @permission_classes((IsAuthenticated,))
    @authentication_classes((TokenAuthentication,))
    def list(self, request, *args, **kwargs):
        serializer = OrderSerializer(Order.objects.opened(), many=True)
        return Response(serializer.data)

此外,create方法不应仅限于经过身份验证的用户。

1 个答案:

答案 0 :(得分:2)

我建议您对Viewset进行标准化:

class OrderViewSet(viewsets.ModelViewSet):
    serializer_class = OrderSerializer
    model = Order

    def get_queryset(self):
        """QuerySet for this entire ModelViewSet will only return 
        orders which are opened.

        """
        return Order.objects.opened()

    @permission_classes((IsAuthenticated,))
    @authentication_classes((TokenAuthentication,))
    def list(self, request, *args, **kwargs):
        return super(OrderViewSet, self).list(request, *args, **kwargs)

经过进一步调查,我查看了TokenAuthentication的源代码,看来如果您根本没有发送身份验证令牌,authenticate()方法会返回None get_authorization_header()方法什么都不返回。因此,如果您从标题中完全删除HTTP_AUTHORIZATION,则这是预期的行为。

我认为这里的目的是不提出异常,以便身份验证可以转移到下一个可能的身份验证类。如果这不是您想要执行的操作,则可以覆盖从authenticate()继承的您自己的类中的TokenAuthentication方法。请参阅下面的代码。

def get_authorization_header(request):
    """
    Return request's 'Authorization:' header, as a bytestring.

    Hide some test client ickyness where the header can be unicode.
    """
    auth = request.META.get('HTTP_AUTHORIZATION', b'')
    if isinstance(auth, type('')):
        # Work around django test client oddness
        auth = auth.encode(HTTP_HEADER_ENCODING)
    return auth

class TokenAuthentication(BaseAuthentication):
    """
    Simple token based authentication.

    Clients should authenticate by passing the token key in the "Authorization"
    HTTP header, prepended with the string "Token ".  For example:

        Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a
    """

    model = Token
    """
    A custom token model may be used, but must have the following properties.

    * key -- The string identifying the token
    * user -- The user to which the token belongs
    """
    def authenticate(self, request):
        auth = get_authorization_header(request).split()

        if not auth or auth[0].lower() != b'token':
            return None

        if len(auth) == 1:
            msg = 'Invalid token header. No credentials provided.'
            raise exceptions.AuthenticationFailed(msg)
        elif len(auth) > 2:
            msg = 'Invalid token header. Token string should not contain spaces.'
            raise exceptions.AuthenticationFailed(msg)

        return self.authenticate_credentials(auth[1])

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

        return (token.user, token)

    def authenticate_header(self, request):
        return 'Token'

最后,要使用401而不是200使令牌失败,您可以执行以下操作:

class YourCustomTokenAuthentication(TokenAuthentication):
    def authenticate(self, request):
        auth = get_authorization_header(request).split()

        if not auth or auth[0].lower() != b'token':
            msg = 'Invalid token header. No credentials provided.'
            raise exceptions.AuthenticationFailed(msg)

        if len(auth) == 1:
            msg = 'Invalid token header. No credentials provided.'
            raise exceptions.AuthenticationFailed(msg)
        elif len(auth) > 2:
            msg = 'Invalid token header. Token string should not contain spaces.'
            raise exceptions.AuthenticationFailed(msg)

        return self.authenticate_credentials(auth[1])