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
相关产品推荐
相关产品推荐

