DRF认证在单元测试数据库中失效问题排查
核心原因
测试环境中authenticate(username=email, password=password)返回None,但check_password验证通过、生产环境正常,大概率和以下几点相关:
1. 自定义User模型的USERNAME_FIELD配置错误
Django的authenticate函数默认使用AUTH_USER_MODEL中USERNAME_FIELD指定的字段作为username参数的匹配字段。如果你的自定义User模型未将USERNAME_FIELD设置为email,那么authenticate(username=email, ...)会去匹配username字段而非email字段——即使邮箱正确,只要username字段不等于邮箱,就会返回None。
检查你的User模型代码,确认是否有如下配置:
class User(AbstractBaseUser, PermissionsMixin): email = models.EmailField(unique=True) # 其他字段... USERNAME_FIELD = 'email' # 必须设置这一行,让Django用邮箱作为登录标识 REQUIRED_FIELDS = [] # 根据需求调整
2. 测试用例中用户创建的密码处理问题
虽然check_password验证通过,但仍需确认BaseTestCase中创建user_1时,是否正确使用set_password方法设置密码(而非直接赋值明文):
# 正确创建方式1 self.user_1 = User.objects.create_user( email='test@example.com', password='12345678' ) # 正确创建方式2 self.user_1 = User(email='test@example.com') self.user_1.set_password('12345678') self.user_1.save()
如果直接赋值self.user_1.password = '12345678',密码不会被加密,authenticate会因密码不匹配返回None(你的情况check_password为True,这条可能性较低,但仍需确认)。
3. 认证后端配置问题
确认settings.py中配置了正确的认证后端,确保支持邮箱登录:
AUTHENTICATION_BACKENDS = [ 'django.contrib.auth.backends.ModelBackend', # 若有自定义后端,需确保它支持通过邮箱认证 ]
默认的ModelBackend会使用USERNAME_FIELD指定的字段进行认证,只要USERNAME_FIELD设置正确,默认后端即可正常工作。
排查步骤
- 优先检查自定义
User模型的USERNAME_FIELD是否设置为email,这是最可能的原因; - 确认测试用例中创建用户时,密码是通过
create_user或set_password加密后保存的; - 在测试用例中添加调试代码,验证用户字段匹配情况:
如果两者不一致,说明print("user_1 username:", self.user_1.username) print("user_1 email:", self.user_1.email)USERNAME_FIELD配置错误,导致authenticate找错了字段。
修复示例
若为USERNAME_FIELD未设置的问题,修改自定义User模型:
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager from django.db import models class UserManager(BaseUserManager): def create_user(self, email, password=None, **extra_fields): if not email: raise ValueError('用户必须提供邮箱') email = self.normalize_email(email) user = self.model(email=email, **extra_fields) user.set_password(password) user.save(using=self._db) return user class User(AbstractBaseUser, PermissionsMixin): email = models.EmailField(verbose_name='邮箱', unique=True) is_active = models.BooleanField(default=True) is_staff = models.BooleanField(default=False) USERNAME_FIELD = 'email' REQUIRED_FIELDS = [] objects = UserManager()
重新运行测试后,authenticate应该能正确找到用户并返回实例。
内容的提问来源于stack exchange,提问作者Selman Faruk Yılmaz

