You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django测试用例中Token认证失败:提示‘Authentication credentials were not provided’

Django APITestCase Token认证失败排查与解决

问题描述

在Django的APITestCase中创建超级测试用户并生成对应Token,发送PUT请求到/api/v1/credit-card接口时,已携带格式正确的Authorization请求头,但始终返回错误:

{'detail': ErrorDetail(string='Authentication credentials were not provided.', code='not_authenticated')}

使用相同Token在Postman中调用该接口可正常工作,尝试过改为POST请求、调用登录函数,问题仍未解决。

相关代码

test.py

class CreditCardTestCase(APITestCase):

    def setUp(self):

        self.user = User.objects.create_superuser(username='test.case',
                                                  email='test.case@gmail.com',
                                                  password='123456')

        if self.user is not None:

            try:

                self.token = Token.objects.get(user=self.user)

            except:

                self.token = Token.objects.create(user=self.user)

            self.headers = {'Authorization': 'Token ' + self.token.key}

    def test_credit_card_with_invalid_number(self):

        print(self.headers)

        response = self.client.put('/api/v1/credit-card', {
            'exp_date': '02/2028',
            'holder': 'Olivia Johnson',
            'number': '1234567891231234',
            'cvv': '999'
        }, **self.headers)

        print(response.data)

        self.assertDictEqual({
            'message': 'Invalid Credit Card Number'
        }, response.data)

view.py

class CreditCardView(APIView):

    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]

    def get(self, request, key):

        credit_card = CreditCardModel.objects.get(pk=key)

        serializer = CreditCardSerializer(credit_card)

        private_key = read_keys('private')

        serializer_data = serializer.data
        serializer_data['number'] = decrypt_credit_card_number(
            private_key, credit_card.number)

        return Response({
            'credit_card': serializer_data
        }, status=HTTP_200_OK)

    def put(self, request):

        try:

            params = request.data

            credit_card = CreditCard(params['number'])

            response = basic_info_verifier(params, credit_card)

            brand_obj = CreditCardBrand.objects.get(
                description__icontains=response['brand'])

            public_key = read_keys('public')

            print(public_key)

            encrypted_number = encrypt_credit_card_number(
                public_key, params['number'])

            credit_card = CreditCardModel(
                exp_date=response['expiration_date'],
                holder=params['holder'],
                number=encrypted_number,
                cvv=params['cvv'] if params['cvv'] else '',
                brand=brand_obj
            )

            credit_card.save()

            return Response({
                'message': 'Credit card registered with success',
                'card_id': credit_card.id
            }, status=HTTP_201_CREATED)

        except BrandNotFound:
            return Response({
                'message': 'Brand Not Found'
            }, status=HTTP_400_BAD_REQUEST)

        except InvalidCreditCardNumberException:
            return Response({
                'message': 'Invalid Credit Card Number'
            }, status=HTTP_400_BAD_REQUEST)

        except InvalidDateException:
            return Response({
                'message': 'Invalid Expiration Date, must be later than now and valid'
            }, status=HTTP_400_BAD_REQUEST)

        except InvalidHolderException:
            return Response({
                'message': 'Invalid Holder Name, must have at least 2 characters'
            }, status=HTTP_400_BAD_REQUEST)

        except:
            return Response({
                'message': 'Error, code failed at card creation'
            }, status=HTTP_500_INTERNAL_SERVER_ERROR)

问题原因及解决办法

1. 请求头传递方式错误

Django测试客户端的put方法中,直接解包**self.headers会把请求头当作表单数据传递,而不是真正的HTTP请求头。正确的做法是通过headers参数传递请求头。

修正后的测试请求代码

def test_credit_card_with_invalid_number(self):
    print(self.headers)
    response = self.client.put(
        '/api/v1/credit-card',
        {
            'exp_date': '02/2028',
            'holder': 'Olivia Johnson',
            'number': '1234567891231234',
            'cvv': '999'
        },
        headers=self.headers  # 明确指定headers参数
    )
    print(response.data)
    self.assertDictEqual({
        'message': 'Invalid Credit Card Number'
    }, response.data)

2. 更简洁的认证方式(可选)

无需手动生成和传递Token,直接使用测试客户端的force_authenticate方法认证用户:

def setUp(self):
    self.user = User.objects.create_superuser(
        username='test.case',
        email='test.case@gmail.com',
        password='123456'
    )
    # 直接认证当前用户,后续请求自动携带认证信息
    self.client.force_authenticate(user=self.user)

这样后续发送请求时就不需要再手动设置Authorization头。

3. 优化Token生成逻辑

原代码中的Token获取逻辑可以简化为get_or_create,同时添加断言确保Token生成成功:

def setUp(self):
    self.user = User.objects.create_superuser(
        username='test.case',
        email='test.case@gmail.com',
        password='123456'
    )
    # 简化Token获取/创建逻辑
    self.token, created = Token.objects.get_or_create(user=self.user)
    # 断言Token存在,避免后续空值问题
    self.assertIsNotNone(self.token)
    self.headers = {'Authorization': f'Token {self.token.key}'}

内容的提问来源于stack exchange,提问作者Augusto

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 12:18:15