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

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方法的返回值不符合要求:

  1. 完全覆盖了父类TokenObtainPairSerializer的validate逻辑,没有生成SimpleJWT规范的access、refresh令牌,反而直接返回了包含Django ORM User模型实例的字典。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 07:34:23