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表中,可正常登录。
修复步骤
配置默认用户模型
在settings.py中添加以下配置,告知Django使用自定义用户模型:AUTH_USER_MODEL = 'api.CustomUser'修复数据库表关联
- 开发环境:若数据无保留价值,可清空数据库,重新执行迁移命令:
python manage.py flush python manage.py makemigrations python manage.py migrate - 生产环境:需创建迁移文件修改外键关联,执行以下步骤:
- 生成修改外键的迁移文件:
python manage.py makemigrations --empty token_blacklist - 编辑生成的迁移文件,添加修改外键指向自定义用户表的代码(示例):
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'), ), ] - 执行迁移:
python manage.py migrate
- 生成修改外键的迁移文件:
- 开发环境:若数据无保留价值,可清空数据库,重新执行迁移命令:
验证修复
创建新用户并尝试登录,确认错误不再出现。
内容的提问来源于stack exchange,提问作者natalie chan
相关产品推荐
相关产品推荐

