DRF自定义JWT序列化器登录报User不可JSON序列化错误
DRF SimpleJWT 登录触发信号报JSON序列化错误问题
问题现象
基于DRF + SimpleJWT开发认证接口,重写TokenObtainPairSerializer的validate方法,计划在用户登录成功获取JWT令牌时触发user_logged_in信号实现登录活动追踪,接口调用时抛出如下错误:
Object of type User is not JSON serializable
现有实现代码
路由配置
path('api/token/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'),
自定义登录视图
class CustomTokenObtainPairView(TokenObtainPairView): serializer_class = CustomTokenObtainPairSerializer
自定义令牌序列化器
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer; from rest_framework_simplejwt.views import TokenObtainPairView; ... class CustomTokenObtainPairSerializer(TokenObtainPairSerializer): def validate(self, attrs): credentials = { self.username_field: attrs.get(self.username_field), 'password': attrs.get('password') } if all(credentials.values()): user = authenticate(request=self.context['request'], **credentials) if user: if not user.is_active: msg = 'User account is disabled.' raise serializers.ValidationError(msg) user_logged_in.send(sender=user.__class__, request=self.context['request'], user=user) return { # 'token': jwt_encode_handler(payload), 'user': user } else: msg = 'Unable to log in with provided credentials.' raise serializers.ValidationError(msg) else: msg = 'Must include "{username_field}" and "password".' msg = msg.format(username_field=self.username_field) raise serializers.ValidationError(msg) @classmethod def get_token(cls, user): token = super().get_token(user) token['username'] = user.username token['first_name'] = user.first_name token['last_name'] = user.last_name token['country'] = user.profile.country token['city'] = user.profile.city token['bio'] = user.profile.bio token['photo'] = json.dumps(str(user.profile.profile_pic)) return token
登录活动记录模型
class UserLoginActivity(models.Model): SUCCESS = 'S' FAILED = 'F' LOGIN_STATUS = ((SUCCESS, 'Success'), (FAILED, 'Failed')) login_IP = models.GenericIPAddressField(null=True, blank=True) login_datetime = models.DateTimeField(auto_now=True) login_username = models.CharField(max_length=40, null=True, blank=True) status = models.CharField(max_length=1, default=SUCCESS, choices=LOGIN_STATUS, null=True, blank=True) user_agent_info = models.CharField(max_length=255) class Meta: verbose_name = 'user_login_activity' verbose_name_plural = 'user_login_activities'
报错根因
报错和信号触发逻辑无关,核心问题是validate方法的返回值不符合要求:
- 完全覆盖了父类
TokenObtainPairSerializer的validate逻辑,没有生成SimpleJWT规范的access、refresh令牌,反而直接返回了包含Django ORM User模型实例的字典。 - DRF的JSON渲染器无法直接序列化ORM模型实例,尝试将User对象转为JSON时就会抛出该错误。
修复方案
不需要手动重写整个认证流程,直接复用父类的认证、令牌生成逻辑,在父类校验通过后触发登录信号即可,修正后的序列化器代码如下:
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer from django.contrib.auth.signals import user_logged_in from rest_framework import serializers import json class CustomTokenObtainPairSerializer(TokenObtainPairSerializer): def validate(self, attrs): # 调用父类validate完成默认的账号校验、令牌生成逻辑 data = super().validate(attrs) user = self.user if not user.is_active: raise serializers.ValidationError('User account is disabled.') # 校验通过后触发登录信号 user_logged_in.send( sender=user.__class__, request=self.context['request'], user=user ) # 返回父类生成的标准令牌响应,不要返回User实例 return data @classmethod def get_token(cls, user): token = super().get_token(user) token['username'] = user.username token['first_name'] = user.first_name token['last_name'] = user.last_name # 增加profile存在性判断,避免无关联profile时报错 if hasattr(user, 'profile'): token['country'] = user.profile.country token['city'] = user.profile.city token['bio'] = user.profile.bio # 不需要额外json.dumps,直接传字符串即可 token['photo'] = str(user.profile.profile_pic) return token
额外优化点
- 不需要手动调用
authenticate方法做账号校验,父类方法已经实现了完整的认证逻辑,认证成功后的用户对象会自动挂载到self.user属性上,直接取用即可。 - 信号接收函数中可以通过传入的
request对象提取客户端IP、User-Agent信息,存入UserLoginActivity表完成登录日志记录。
内容的提问来源于stack exchange,提问作者Emm
相关产品推荐
相关产品推荐

