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

Django+SimpleJWT新用户登录失败:IntegrityError外键约束问题

问题:新创建用户登录触发IntegrityError外键约束问题

问题背景

开发Web应用采用以下技术栈:Django后端、Django Rest Framework(DRF)作为API框架、React前端、PostgreSQL数据库,使用SimpleJWT实现用户认证。当前遇到的问题:

  • 前端可正常创建用户,数据库中存在新用户记录;
  • 新创建用户无法登录,触发控制台报错;
  • 较早创建的同表用户可正常通过前端和DRF登录。

错误信息

Django端持续抛出以下错误(user_id值随报错用户变化):

IntegrityError at /api/token/
insert or update on table "token_blacklist_outstandingtoken" violates foreign key constraint "token_blacklist_outs_user_id_83bc629a_fk_auth_user"
DETAIL:  Key (user_id)=(4) is not present in table "auth_user".

相关代码

settings.py(SimpleJWT配置部分)

SIMPLE_JWT = {
    "ACCESS_TOKEN_LIFETIME": timedelta(minutes=30),
    "REFRESH_TOKEN_LIFETIME": timedelta(days=90),
    "ROTATE_REFRESH_TOKENS": True,
    "BLACKLIST_AFTER_ROTATION": True,
    "UPDATE_LAST_LOGIN": True,
    # FOR SECURITY REASON (ABOVE)
    "ALGORITHM": "HS256",
    "SIGNING_KEY": SECRET_KEY,
    "VERIFYING_KEY": "",
    "AUDIENCE": None,
    "ISSUER": None,
    "JSON_ENCODER": None,
    "JWK_URL": None,
    "LEEWAY": 0,

    "AUTH_HEADER_TYPES": ("Bearer",),
    "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
    "USER_ID_FIELD": "id",
    "USER_ID_CLAIM": "user_id",
    "USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule",

    "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
    "TOKEN_TYPE_CLAIM": "token_type",
    "TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",

    "JTI_CLAIM": "jti",

    "SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
    "SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),
    "SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),

    "TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",
    "TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer",
    "TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer",
    "TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer",
    "SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer",
    "SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer",
}

api/models.py

from django.db import models 
from django.contrib.auth.models import AbstractUser  
class CustomUser(AbstractUser):     
    username = models.CharField(max_length=100)     
    email = models.EmailField(max_length=100, unique=True)       
    USERNAME_FIELD = 'email'     
    REQUIRED_FIELDS = ['username']

api/serializers.py

from api.models import CustomUser
from rest_framework import serializers
from django.contrib.auth.password_validation import validate_password
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

class CustomUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = CustomUser
        fields = ('id', 'username', 'email')

class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
    @classmethod
    def get_token(cls, user):
        token = super().get_token(user)
        
        token['username'] = user.username
        token['email'] = user.email
        return token


class RegisterSerializer(serializers.ModelSerializer):
    password = serializers.CharField(
        write_only=True, required=True, validators=[validate_password])
    confirmed_password = serializers.CharField(write_only=True, required=True)

    class Meta:
        model = CustomUser
        fields = ('email', 'username', 'password', 'confirmed_password')

    def validate(self, attrs):
        if attrs['password'] != attrs['confirmed_password']:
            raise serializers.ValidationError(
                {"password": "Password fields didn't match."})
        return attrs

    def create(self, validated_data):
        user = CustomUser.objects.create(
            username=validated_data['username'],
            email=validated_data['email']
        )
        user.set_password(validated_data['password'])
        user.save()
        return user

api/urls.py

from django.urls import path
from . import views
from .views import MyTokenObtainPairView
from rest_framework_simplejwt.views import(
    TokenObtainPairView,
    TokenRefreshView,
)

urlpatterns = [
    path('', views.getRoutes),

    path('token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
    path('register/', views.RegisterView.as_view(), name='auth_register'),
]

api/views.py

from django.http import JsonResponse
from rest_framework.response import Response
from rest_framework.decorators import api_view
from rest_framework import generics
from api.models import CustomUser
from rest_framework.permissions import AllowAny
from .serializers import MyTokenObtainPairSerializer, RegisterSerializer

from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView

class MyTokenObtainPairView(TokenObtainPairView):
    serializer_class = MyTokenObtainPairSerializer

class RegisterView(generics.CreateAPIView):
    queryset = CustomUser.objects.all()
    permission_classes = (AllowAny,)
    serializer_class = RegisterSerializer


@api_view(['GET'])
def getRoutes(request):
    routes = [
    '/api/token',
    '/api/register',
    '/api/token/refresh'
    ]
    return Response(routes)

解决方案

核心原因

使用了自定义用户模型CustomUser,但未在Django配置中声明它为默认认证用户模型,导致SimpleJWT的token黑名单表(token_blacklist_outstandingtoken)外键仍指向默认的auth_user表,而新用户存储在自定义用户表中,触发外键约束冲突。老用户可能是在自定义模型生效前创建的,因此存在于auth_user表中,可正常登录。

修复步骤

  1. 配置默认用户模型
    在settings.py中添加以下配置,告知Django使用自定义用户模型:

    AUTH_USER_MODEL = 'api.CustomUser'
    
  2. 修复数据库表关联

    • 开发环境:若数据无保留价值,可清空数据库,重新执行迁移命令:
      python manage.py flush
      python manage.py makemigrations
      python manage.py migrate
      
    • 生产环境:需创建迁移文件修改外键关联,执行以下步骤:
      1. 生成修改外键的迁移文件:
        python manage.py makemigrations --empty token_blacklist
        
      2. 编辑生成的迁移文件,添加修改外键指向自定义用户表的代码(示例):
        from django.db import migrations, models
        import django.db.models.deletion
        
        class Migration(migrations.Migration):
            dependencies = [
                ('api', '__latest__'),  # 替换为api app的最新迁移版本号
                ('token_blacklist', '__latest__'),  # 替换为token_blacklist的最新迁移版本号
            ]
        
            operations = [
                migrations.AlterField(
                    model_name='outstandingtoken',
                    name='user',
                    field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.customuser'),
                ),
                migrations.AlterField(
                    model_name='blacklistedtoken',
                    name='token',
                    field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='token_blacklist.outstandingtoken'),
                ),
            ]
        
      3. 执行迁移:
        python manage.py migrate
        
  3. 验证修复
    创建新用户并尝试登录,确认错误不再出现。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 17:44:50