如何在Django自定义用户模型中实现无哈希算法的密码字段与认证功能
实现Django自定义用户模型的明文密码认证
我明白你的需求——因为要适配遗留数据库,所以得让Django的自定义用户模型跳过密码哈希,直接用明文存储和认证。虽然这确实有安全风险,但既然你的应用不对外公开,那咱们就来实现这个方案。
下面是修改后的完整代码,核心是重写AbstractBaseUser中处理密码的两个关键方法:
from django.db import models from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin from django.utils import timezone from django.contrib.auth.hashers import UNUSABLE_PASSWORD_PREFIX class AccountManager(BaseUserManager): use_in_migrations = True def _create_user(self, email, name, phone, password, **extra_fields): values = [email, name, phone] field_value_map = dict(zip(self.model.REQUIRED_FIELDS, values)) for field_name, value in field_value_map.items(): if not value: raise ValueError('The {} value must be set'.format(field_name)) email = self.normalize_email(email) user = self.model( email=email, name=name, phone=phone, **extra_fields ) user.set_password(password) # 这里还是调用set_password,但我们已经重写了逻辑 user.save(using=self._db) return user def create_user(self, email, name, phone, password=None, **extra_fields): extra_fields.setdefault('is_staff', False) extra_fields.setdefault('is_superuser', False) return self._create_user(email, name, phone, password, **extra_fields) def create_superuser(self, email, name, phone, password=None, **extra_fields): extra_fields.setdefault('is_staff', True) extra_fields.setdefault('is_superuser', True) if extra_fields.get('is_staff') is not True: raise ValueError('Superuser must have is_staff=True.') if extra_fields.get('is_superuser') is not True: raise ValueError('Superuser must have is_superuser=True.') return self._create_user(email, name, phone, password, **extra_fields) class Account(AbstractBaseUser, PermissionsMixin): email = models.EmailField(unique=True) name = models.CharField(max_length=150) phone = models.CharField(max_length=50) date_of_birth = models.DateField(blank=True, null=True) picture = models.ImageField(blank=True, null=True) is_staff = models.BooleanField(default=False) is_active = models.BooleanField(default=True) date_joined = models.DateTimeField(default=timezone.now) last_login = models.DateTimeField(null=True) objects = AccountManager() USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['name', 'phone'] def get_full_name(self): return self.name def get_short_name(self): return self.name.split()[0] # 重写set_password:直接存储明文,跳过哈希 def set_password(self, raw_password): if raw_password is None: self.set_unusable_password() else: # 直接赋值明文,不做任何哈希处理 self.password = raw_password self._password = raw_password # 保留Django内部的密码跟踪属性 # 重写check_password:直接对比明文,不验证哈希 def check_password(self, raw_password): if self.password is None: return False # 保留不可用密码的处理逻辑(比如用户未设置密码的情况) if self.password.startswith(UNUSABLE_PASSWORD_PREFIX): return False # 直接对比输入的明文和数据库存储的密码 return raw_password == self.password
关键修改说明
- 重写
set_password:原来的父类方法会对明文密码做哈希处理,现在我们直接把明文赋值给password字段,同时保留_password属性(Django用来跟踪密码是否修改的内部字段)。如果传入None,依然调用set_unusable_password()保持原有逻辑。 - 重写
check_password:默认方法会尝试用哈希算法验证密码,现在我们直接对比输入的明文和数据库中存储的明文。同时保留了对“不可用密码”的处理,确保和原有逻辑一致。 - 管理器无需修改:原来的创建用户逻辑中调用的
user.set_password(password)会自动使用我们重写后的方法,直接存储明文。
额外注意事项
- 即使应用不对外公开,也要严格控制数据库的访问权限,避免明文密码泄露。
- 遗留数据库中的明文密码可以直接迁移过来,不需要做任何转换。
- 现有登录逻辑(比如使用
authenticate函数)不需要修改,会自动调用我们重写的check_password方法完成认证。
内容的提问来源于stack exchange,提问作者Mohammad Esmaeili
相关产品推荐
相关产品推荐

