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

Django REST Framework自定义认证登录后状态无法保持问题求助

问题排查与解决方案

我来帮你梳理下这个认证状态丢失的问题,咱们一步步拆解关键问题点:

1. 先确认自定义认证类的启用配置

你已经写好了PhoneAuthentication,但DRF不会自动识别并启用它,必须手动配置:

  • 全局配置(推荐,在settings.py中添加):
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'your_app_name.auth.PhoneAuthentication',  # 替换成你的app实际路径
        # 如果你需要同时支持session认证,可保留下面这行:
        # 'rest_framework.authentication.SessionAuthentication',
    ],
}
  • 视图级单独配置:如果只想在特定受保护视图启用,可在视图类中添加:
from rest_framework.permissions import IsAuthenticated

class YourProtectedView(APIView):
    authentication_classes = [PhoneAuthentication]
    permission_classes = [IsAuthenticated]
    # ... 你的视图逻辑

如果没做这个配置,DRF会用默认认证类,自然识别不了你的Token规则。

2. 登录视图的Token生成逻辑不匹配

你的PhoneAuthentication是基于JWT解析认证的,但当前LoginView直接返回user.token(看起来是用户模型的字段),这和JWT的格式不兼容,会导致后续请求解析Token时失败。

修改LoginView,添加JWT生成逻辑:

import jwt
from datetime import datetime, timedelta
from django.conf import settings

class LoginView(APIView):
    serializer_class = LoginSerializer
    permission_classes = (AllowAny,)

    def post(self, request, format=None):
        phonenumber = request.data.get('phonenumber', None)
        first_token = request.data.get('first_token', None)

        try:
            user = User.objects.get(phonenumber=phonenumber)
        except User.DoesNotExist:
            return Response({'detail': 'user does not exists'}, status=status.HTTP_404_NOT_FOUND)

        if first_token == user.first_token.token:
            # 生成符合JWT规范的Token
            payload = {
                'id': user.pk,
                'exp': datetime.utcnow() + timedelta(hours=24),  # 设置24小时过期
                'iat': datetime.utcnow()
            }
            # 用SECRET_KEY签名生成Token
            token = jwt.encode(payload, settings.SECRET_KEY, algorithm='HS256')

            user_info = {
                'phonenumber': user.phonenumber,
                'username': user.username,
                'token': token,
                'is_admin': user.is_admin,
            }
            return Response(user_info, status=status.HTTP_200_OK)
        else:
            return Response({'detail': 'Invalid first token'}, status=status.HTTP_401_UNAUTHORIZED)

注:如果你的User模型里的token字段没用了,可以直接删掉,JWT是动态生成的不需要存在数据库。

3. 测试框架的请求头必须携带Token

测试后续请求时,一定要在Authorization请求头里按照Token <你的JWT token>的格式携带认证信息,比如用DRF的APIClient测试:

from rest_framework.test import APIClient

client = APIClient()
# 先登录获取Token
login_res = client.post('/login/', {'phonenumber': '你的测试手机号', 'first_token': '测试token'})
token = login_res.data['token']
# 给后续请求设置认证头
client.credentials(HTTP_AUTHORIZATION=f'Token {token}')
# 现在访问受保护视图就能通过认证了
protected_res = client.get('/your-protected-path/')

如果测试时没加这个头,PhoneAuthentication会因为获取不到认证信息直接返回None,导致认证失败。

4. 登录视图中login(request, user)的作用

你调用了Django原生的login方法,这是用于session认证的,但你的自定义认证是Token-based的,两者是独立机制。如果不需要支持session认证,可以删掉这个调用,避免混淆;如果需要同时支持两种认证,记得在settings里同时配置对应的认证类。

5. 自定义认证类的细节优化

给你的PhoneAuthentication加一点细节,让错误更清晰、更安全:

class PhoneAuthentication(authentication.BaseAuthentication):
    authentication_header_prefix = 'Token'

    def authenticate(self, request):
        request.user = None
        auth_header = authentication.get_authorization_header(request).split()
        auth_header_prefix = self.authentication_header_prefix.lower()

        if not auth_header:
            return None
        if len(auth_header) == 1:
            return None
        elif len(auth_header) > 2:
            return None

        prefix = auth_header[0].decode('utf-8')
        token = auth_header[1].decode('utf-8')

        if prefix.lower() != auth_header_prefix:
            return None

        return self._authenticate_credentials(request, token)

    def _authenticate_credentials(self, request, token):
        try:
            # 指定算法更安全,避免潜在漏洞
            payload = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
        except jwt.ExpiredSignatureError:
            raise exceptions.AuthenticationFailed("Token has expired")
        except jwt.InvalidTokenError:
            raise exceptions.AuthenticationFailed("Invalid authentication. Could not decode token")

        try:
            user = User.objects.get(pk=payload['id'])
        except User.DoesNotExist:
            raise exceptions.AuthenticationFailed('No such user')

        # 检查用户是否激活
        if not user.is_active:
            raise exceptions.AuthenticationFailed('User is inactive')

        return (user, token)

按照上面的步骤调整后,应该就能解决测试时认证状态不保留的问题了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:05:01