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

Django Rest Framework APIClient登录成功但访问接口仍返回401

问题:DRF测试中登录成功但访问受保护接口仍返回401

我编写了测试函数验证用户登录功能,使用DRF的APIClient完成登录,打印client.login(username='instructor', password='asdf')返回True,表明用户已成功登录,但调用受保护的recommend-list接口的GET请求仍返回401状态码。

测试函数代码

def test_user_authentication(self):
    client = APIClient()
    user_data = {
        'username': 'instructor',
        'email': 'myemail@example.com',
        'password': 'asdf'
    }
    client.post(reverse('user-list'), user_data, format='json')
    self.assertTrue(User.objects.filter(username='instructor').exists())
    print(client.login(username='instructor', password='asdf'))
    response = client.get(reverse('recommend-list'))
    self.assertEqual(response.status_code, 200)

用户创建视图集代码

class UserViewSet(viewsets.ViewSet):
    def create(self, request):
        serializer = UserSerializer(data=request.data)
        User = get_user_model()

        if serializer.is_valid():
            validated_data = serializer.validated_data

            try:
                user = User.objects.create_user(
                    username=validated_data['username'],
                    email=validated_data['email'],
                    password=validated_data['password']
                )

                return Response({'id': user.id, 'username': user.username}, status=status.HTTP_201_CREATED)
            except IntegrityError:
                return Response({'error': 'Username is already taken.'}, status=status.HTTP_400_BAD_REQUEST)

        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

受保护的推荐接口视图集代码

class RecommendViewSet(viewsets.GenericViewSet):
    authentication_classes = [BasicAuthentication]
    permission_classes = [IsAuthenticated]

    def list(self, request):
        user_id = request.user.id
        movies = recommend_movies_for_user(user_id)
        return Response({"message": movies}, status=status.HTTP_200_OK)

用户序列化器代码

class UserSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(read_only=True)
    username = serializers.CharField(max_length=150)
    email = serializers.EmailField()
    password = serializers.CharField(write_only=True)
    follower_list = serializers.SerializerMethodField()
    following_list = serializers.SerializerMethodField()

    class Meta:
        model = User
        fields = ['id', 'username', 'email', 'password',
                  'follower_list', 'following_list']

    def retrieve(self, instance):
        return instance

    def create(self, validated_data):
        user = User.objects.create_user(**validated_data)
        return user

    def get_follower_list(self, obj):
        follower = obj.followers_set.all()
        return FollowSerializer(follower, many=True).data

    def get_following_list(self, obj):
        following = obj.following_set.all()
        return FollowSerializer(following, many=True).data

问题原因与解决方案

原因

APIClient的login()方法默认是为SessionAuthentication设计的,它会设置会话cookie,但你的RecommendViewSet只配置了BasicAuthentication,不会识别会话登录状态,所以即使login()返回True,接口依然会要求Basic Auth凭证,返回401。

解决方案

有两种可行的修改方式:

方案1:让推荐接口支持SessionAuthentication

修改RecommendViewSet的authentication_classes,同时包含SessionAuthentication:

from rest_framework.authentication import SessionAuthentication, BasicAuthentication

class RecommendViewSet(viewsets.GenericViewSet):
    authentication_classes = [SessionAuthentication, BasicAuthentication]
    permission_classes = [IsAuthenticated]

    # 其余代码不变

这样测试里的client.login()就能正常生效,接口会识别会话登录状态。

方案2:在测试中手动添加Basic Auth凭证

保持接口的authentication_classes不变,修改测试函数,使用client.credentials()添加Basic Auth的HTTP头:

import base64

def test_user_authentication(self):
    client = APIClient()
    user_data = {
        'username': 'instructor',
        'email': 'myemail@example.com',
        'password': 'asdf'
    }
    client.post(reverse('user-list'), user_data, format='json')
    self.assertTrue(User.objects.filter(username='instructor').exists())
    
    # 添加Basic Auth凭证
    credentials = base64.b64encode(b'instructor:asdf').decode()
    client.credentials(HTTP_AUTHORIZATION=f'Basic {credentials}')
    
    response = client.get(reverse('recommend-list'))
    self.assertEqual(response.status_code, 200)

这种方式直接符合接口要求的BasicAuthentication认证方式,不需要修改接口配置。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 01:45:56