如何解决Django Admin中自定义User模型密码明文存储问题?
解决Django自定义User模型Admin页面密码明文存储问题
我使用AbstractBaseUser定义了自定义User模型,其他功能均正常,但在Django原生Admin页面设置或更新用户密码时,密码会以明文形式存储而非哈希处理。相关代码如下:
models.py 代码
#models.py class UserManager(BaseUserManager): def create_user(self, email, firstname, lastname, contact, password): if not email: raise ValueError('You must provide an email address') if not contact: raise ValueError('You must provide a contact number') email = self.normalize_email(email) user = self.model(email=email, firstname=firstname, lastname=lastname, contact=contact) user.set_password(password) user.save(using=self._db) return user def create_superuser(self, email, firstname, lastname, contact, password): user = self.create_user(email, firstname, lastname, contact, password) user.is_admin = True user.save(using=self._db) return user class Users(AbstractBaseUser): id = models.AutoField(primary_key=True) firstname = models.CharField(max_length=50) lastname = models.CharField(max_length=50) contact = models.CharField(max_length=10, unique=True) email = models.EmailField(unique=True) created_at = models.DateTimeField(auto_now_add=True) is_staff = models.BooleanField(default=True) is_admin = models.BooleanField(default=False) USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['firstname', 'lastname', 'contact'] objects = UserManager() def save(self, *args, **kwargs): if not self.firstname or not self.lastname or not self.contact or not self.email or not self.password: raise ValueError('All required fields must be provided') super().save(*args, **kwargs) def __str__(self): return str(self.id) + '. ' + self.firstname + ' ' + self.lastname def has_perm(self, perm, obj=None): return self.is_admin def has_module_perms(self, app_label): return True class Meta: db_table = 'users' verbose_name_plural = 'Users'
admin.py 代码
#admin.py from django.contrib import admin from .models import * admin.site.site_header = 'ROI Administration' admin.site.site_title = 'ROI | Admin Panel' admin.site.index_title = 'ROI Databases' class UsersAdmin(admin.ModelAdmin): ordering = ['id'] readonly_fields = ['id', 'created_at', 'last_login'] fieldsets = [ (None, {'fields': ['id', 'firstname', 'lastname', 'email', 'contact', 'last_login', 'created_at', 'is_staff', 'is_admin', 'password']}), ] admin.site.register(Users, UsersAdmin)
问题原因
直接在Admin的fieldsets中包含password字段,会让Admin将输入的明文直接存入数据库,绕过了Django内置的密码哈希逻辑(即没有调用set_password方法生成哈希值)。
解决方案
方案1:移除Admin中直接显示的password字段
修改UsersAdmin的fieldsets,去掉password字段:
class UsersAdmin(admin.ModelAdmin): ordering = ['id'] readonly_fields = ['id', 'created_at', 'last_login'] fieldsets = [ (None, {'fields': ['id', 'firstname', 'lastname', 'email', 'contact', 'last_login', 'created_at', 'is_staff', 'is_admin']}), ]
此时Admin页面不会显示密码输入框,如需修改密码,可使用Django自带的"修改密码"功能(通常在用户详情页的右上角)。
方案2:继承UserAdmin类(推荐)
Django的django.contrib.auth.admin.UserAdmin已经封装了密码哈希、密码确认等完整逻辑,让自定义Admin类继承它,自动获得正确的密码处理功能:
from django.contrib import admin from django.contrib.auth.admin import UserAdmin from .models import Users admin.site.site_header = 'ROI Administration' admin.site.site_title = 'ROI | Admin Panel' admin.site.index_title = 'ROI Databases' class UsersAdmin(UserAdmin): ordering = ['id'] readonly_fields = ['id', 'created_at', 'last_login'] # 新增用户时的字段配置(包含密码确认框) add_fieldsets = ( (None, { 'classes': ('wide',), 'fields': ('email', 'firstname', 'lastname', 'contact', 'password1', 'password2', 'is_staff', 'is_admin'), }), ) # 编辑用户时的字段配置(隐藏密码,使用系统默认修改入口) fieldsets = ( (None, {'fields': ('id', 'email', 'firstname', 'lastname', 'contact', 'last_login', 'created_at', 'is_staff', 'is_admin')}), ) admin.site.register(Users, UsersAdmin)
这样新增用户时会显示两个密码输入框(用于确认),保存时自动调用set_password生成哈希值;编辑用户时则隐藏密码字段,通过系统自带的入口修改密码。
方案3:修复自定义save方法的潜在问题
当前Users模型的save方法检查了self.password是否存在,但当通过Admin修改用户不修改密码时,self.password是已哈希的值,不会有问题。不过如果想更严谨,可以去掉密码的检查逻辑——因为create_user方法已经确保了密码必填,Admin的add_fieldsets也会强制输入密码,无需在save方法中重复验证:
def save(self, *args, **kwargs): # 去掉密码检查 if not self.firstname or not self.lastname or not self.contact or not self.email: raise ValueError('All required fields must be provided') super().save(*args, **kwargs)
内容的提问来源于stack exchange,提问作者Mohit Aswani
相关产品推荐
相关产品推荐

