Django自定义用户模型登录失败:手机号与密码错误排查求助
Django手机号登录失败及密码加密问题解决
问题背景
使用手机号和密码登录Django系统时失败,前端提示invalid phone number data;Admin面板查看用户时,密码字段显示Invalid password or unknown encryption algorithm。已自定义User模型,集成ghasedakpack实现OTP注册,配置了多认证后端。
核心问题分析
- 密码未加密存储:注册时直接通过
User.objects.create()存入明文密码,未使用Django内置的密码加密机制,导致authenticate无法验证密码,同时Admin无法识别密码格式。 - 自定义User模型冗余字段:手动定义了
password字段,覆盖了AbstractBaseUser提供的加密密码字段,破坏了Django的密码管理逻辑。 - OTP流程用户创建逻辑缺陷:OtpView中使用
get_or_create可能创建无密码用户,导致后续登录异常。
分步解决方案
1. 修复自定义User模型
移除手动定义的password字段,依赖AbstractBaseUser内置的加密密码字段:
class User(AbstractBaseUser): email = models.EmailField( verbose_name="آدرس ایمیل", max_length=257, unique=True, blank=True, null=True ) fullname = models.CharField(max_length=50, verbose_name="نام کامل") phone = models.CharField(max_length=11, unique=True, verbose_name="تلفن") # 移除冗余的password字段 is_active = models.BooleanField(default=True) is_admin = models.BooleanField(default=False) objects = UserManager() USERNAME_FIELD = "phone" REQUIRED_FIELDS = [] # 其余方法保持不变...
2. 实现正确的UserManager
确保自定义UserManager包含create_user方法,自动处理密码加密:
from django.contrib.auth.base_user import BaseUserManager class UserManager(BaseUserManager): def create_user(self, phone, password=None, **extra_fields): if not phone: raise ValueError('必须提供手机号') user = self.model(phone=phone, **extra_fields) user.set_password(password) # 自动加密密码 user.save(using=self._db) return user def create_superuser(self, phone, password=None, **extra_fields): extra_fields.setdefault('is_admin', True) extra_fields.setdefault('is_active', True) if extra_fields.get('is_admin') is not True: raise ValueError('超级用户必须设置is_admin=True') return self.create_user(phone, password, **extra_fields)
3. 修复注册视图的用户创建逻辑
替换User.objects.create()为create_user方法,确保密码加密:
# RegisterView的post方法中,替换原用户创建代码 user = User.objects.create_user( phone=cd['phone'], fullname=cd['fullname'], email=cd['email'], password=cd['password'], )
4. 修复OTP验证视图的登录逻辑
注册流程已创建用户,OTP验证时直接获取已存在用户,避免创建无密码用户:
def post(self, request): token = request.GET.get('token') form = OtpForm(request.POST) if form.is_valid(): cd = form.cleaned_data otp_obj = Otp.objects.filter(code=cd['code'], token=token).first() if otp_obj: # 直接获取注册时创建的用户 user = User.objects.get(phone=otp_obj.phone) # 指定手机号认证后端登录 login(request, user, backend='account.authentication.PhoneAuthenticationBackend') otp_obj.delete() return redirect('home:home') else: form.add_error('code', '验证码无效或已过期') else: form.add_error('code', '验证码格式错误') return render(request, 'account/otp.html', {'form': form})
5. 确保手机号认证后端正确实现
验证PhoneAuthenticationBackend逻辑正确,能通过手机号和密码验证用户:
from django.contrib.auth.backends import BaseBackend from .models import User class PhoneAuthenticationBackend(BaseBackend): def authenticate(self, request, username=None, password=None, **kwargs): try: # 用username参数匹配手机号 user = User.objects.get(phone=username) if user.check_password(password): return user except User.DoesNotExist: return None def get_user(self, user_id): try: return User.objects.get(pk=user_id) except User.DoesNotExist: return None
6. 清理错误数据
对之前明文存储密码的用户,需更新密码为加密格式(或删除后重新注册):
# 进入Django shell执行:python manage.py shell from account.models import User # 示例:更新指定用户密码 user = User.objects.get(phone='你的测试手机号') user.set_password('用户的原密码') user.save()
内容的提问来源于stack exchange,提问作者Elyas Shavikloo
相关产品推荐
相关产品推荐

