前言
搭建Django后台、用户登录系统时,几乎所有人都会遇到同一个问题:用户密码如何存储?
不少新手直接使用MD5、SHA256简单哈希,或者自己手写加盐逻辑,上线后埋下严重安全隐患。
Django本身内置完善的密码认证体系,但很多开发者不理解底层原理,随意替换加密算法,造成安全降级、密码迁移、前后端兼容等一系列问题。
本文围绕Django登录、用户密码存储场景,讲清楚各类哈希算法优劣、Django原生机制、最佳实践、避坑要点,附带可直接落地的配置代码。
适用范围:Django3.2 / 4.2 / 5.x,Web后台登录、API接口登录、Admin后台用户体系。
一、先理清核心原则:登录密码不能用什么?
❌ 绝对不推荐
- MD5
已被破解,存在大量彩虹表,仅适合文件校验,严禁存储密码。 - SHA1 / SHA256 / SHA512(单纯哈希、固定加盐)
属于快速哈希算法,GPU可以暴力穷举。哪怕自定义salt,迭代次数只有1次,抵御暴力破解能力极差。 - 自己手写加盐SHA
容易出现编码错误、salt管理混乱、迭代次数不足、随机盐丢失等问题,安全性远低于成熟算法。
✅ 适合密码存储的算法特征
密码哈希算法必须满足:
- 慢速哈希:计算耗时可控,大幅降低暴力破解速度;
- 自动随机盐:每条密码使用独立随机盐,防止彩虹表批量破解;
- 可配置迭代次数:硬件性能提升后可以调高迭代轮数;
- 输出字符串自带算法标识、盐、迭代参数,方便后续升级迁移。
二、Django 内置支持哪些密码算法?
Djangodjango.contrib.auth原生支持多种密码哈希器,配置项位于settings.py的PASSWORD_HASHERS。
默认优先级顺序(Django4.x/5.x):
PASSWORD_HASHERS=['django.contrib.auth.hashers.Argon2PasswordHasher','django.contrib.auth.hashers.PBKDF2PasswordHasher','django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher','django.contrib.auth.hashers.BCryptSHA256PasswordHasher','django.contrib.auth.hashers.ScryptPasswordHasher',]逐个解读:
- Argon2PasswordHasher(首选推荐)
Argon2 是密码哈希竞赛获胜算法,抵抗GPU、ASIC暴力破解能力最强,内存开销高,很难大规模并行爆破。 - PBKDF2PasswordHasher(Django历史默认方案,兼容性最强)
基于HMAC-SHA256,稳定、部署无额外依赖,服务器资源紧张场景首选。 - BCryptSHA256PasswordHasher
经典老牌算法,注意原生bcrypt存在72字节长度限制,Django封装版先sha256预处理规避限制。 - ScryptPasswordHasher
和Argon2思路类似,高内存消耗,适合硬件资源充足服务器。
重点:Django不会简单保存哈希字符串,数据库存储格式示例:
argon2$v=19$m=102400,t=8,p=2$xxxx随机盐$xxxx摘要
字符串内置:算法版本、内存参数、迭代次数、并行度、随机盐、哈希结果,后期平滑升级无需迁移旧密码。
三、方案选型对比(Django登录场景)
| 算法 | 安全性 | 依赖 | 服务器资源消耗 | 适用场景 |
|---|---|---|---|---|
| Argon2 | ⭐⭐⭐⭐⭐ | 需要安装argon2-cffi | 中等偏高 | 新项目首选,服务器CPU内存充足 |
| PBKDF2(SHA256) | ⭐⭐⭐⭐ | 无第三方依赖,Python内置 | 较低 | 老旧服务器、容器资源受限、不想额外安装包 |
| BCryptSHA256 | ⭐⭐⭐⭐ | 需要bcrypt库 | 中等 | 老项目迁移,团队熟悉bcrypt |
| Scrypt | ⭐⭐⭐⭐⭐ | 需要scrypt库 | 高 | 硬件富余,对安全要求极高的内部系统 |
| SHA256自定义加盐 | ⭐ | 无依赖 | 极低 | 禁止用于密码存储 |
四、落地配置教程
方案1:新项目首选 Argon2
- 安装依赖
pipinstallargon2-cffi- settings.py 配置,将Argon2放在第一位(新建用户默认使用首位哈希器)
PASSWORD_HASHERS=['django.contrib.auth.hashers.Argon2PasswordHasher','django.contrib.auth.hashers.PBKDF2PasswordHasherher','django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher','django.contrib.auth.hashers.BCryptSHA256PasswordHasher',]# 可选:调整Argon2参数(内存、迭代次数,越高越安全、越耗CPU)# 一般默认参数足够业务使用,不要盲目调太高引发接口超时方案2:不想安装第三方库,使用 PBKDF2(零依赖)
无需额外安装包,直接配置:
PASSWORD_HASHERS=['django.contrib.auth.hashers.PBKDF2PasswordHasher','django.contrib.auth.hashers.Argon2PasswordHasher','django.contrib.auth.hashers.BCryptSHA256PasswordHasher',]五、Django 密码自动升级机制(非常重要)
很多人不知道Django自带密码平滑升级能力:
当系统PASSWORD_HASHERS首位算法更新后:
- 用户登录时,使用旧算法校验密码;
- 校验成功后,自动使用新算法重新哈希,更新数据库password字段;
- 无需一次性批量迁移全部历史数据,用户登录时渐进升级。
这是自研哈希方案很难实现的优势。
六、在Django视图/API中手动校验密码(前后端分离场景)
如果你使用前后端分离,不依赖LoginView,手动实现登录逻辑:
fromdjango.contrib.authimportauthenticatefromdjango.contrib.auth.hashersimportmake_password,check_passwordfromdjango.httpimportJsonResponsedeflogin_api(request):ifrequest.method=="POST":username=request.POST.get("username")password=request.POST.get("password")# 方式1:标准authenticate(推荐,兼容User模型、自动哈希校验)user=authenticate(username=username,password=password)ifuser:# 登录成功,生成session或tokenreturnJsonResponse({"code":0,"msg":"登录成功"})else:returnJsonResponse({"code":-1,"msg":"账号或密码错误"})手动生成密码(创建用户、重置密码场景)
fromdjango.contrib.auth.hashersimportmake_password raw_pwd="123456"pwd_hash=make_password(raw_pwd)print(pwd_hash)# 直接存入User.password字段手动校验密码(已有hash字符串场景)
fromdjango.contrib.auth.hashersimportcheck_password ok=check_password("原始密码",db_password_hash)⚠️ 禁止做法:不要自己拼接盐+密码调用hashlib.sha256,不要覆盖
make_password底层逻辑。
七、配套安全策略(光选加密算法远远不够)
- 开启密码复杂度校验
# settings.pyAUTH_PASSWORD_VALIDATORS=[{'NAME':'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',},{'NAME':'django.contrib.auth.password_validation.MinimumLengthValidator','OPTIONS':{'min_length':8,}},{'NAME':'django.contrib.auth.password_validation.CommonPasswordValidator',},{'NAME':'django.contrib.auth.password_validation.NumericPasswordValidator',},]登录接口限流,防止暴力猜密码
结合django-ratelimit或者自定义限流,限制同一IP短时间登录失败次数。HTTPS 强制开启
原始密码在网络明文传输,哈希再安全也无意义。不要在日志打印原始密码、不要传输明文哈希给前端。
八、高频踩坑清单
坑1:混淆「数据签名哈希」和「密码哈希」
接口签名、文件校验可以用SHA256;用户登录密码不能直接SHA256,两者设计目标完全不同。
坑2:前后端分离项目,前端先加密再传给后端
❌ 错误思路:前端SHA256加密密码传到后端
攻击者抓包拿到哈希值,即可直接使用哈希登录,等同于明文。
✅ 正确:前端传递原始密码(HTTPS),后端做哈希处理。
坑3:随意更换PASSWORD_HASHERS顺序,旧用户无法登录
不要直接删除旧哈希器,只能把新算法放最前面,旧算法保留在列表用于兼容历史密码。
坑4:自行存储盐字段
Django哈希字符串内置随机盐,不需要单独建字段保存salt,不要重复造轮子。
坑5:Argon2安装失败
部分国内环境pip安装argon2-cffi容易出错,资源受限服务器直接选用PBKDF2规避依赖问题。
九、新旧系统迁移方案
如果老项目密码使用MD5/SHA256存储:
- 在
PASSWORD_HASHERS末尾添加自定义哈希器兼容旧密码; - 用户登录校验通过后,自动用新算法重写密码;
- 等待大部分用户完成登录迁移,下线旧算法。
总结
- Django登录密码存储优先选用Argon2;服务器无法安装依赖,选择PBKDF2SHA256;
- 绝对不要手写SHA256+固定盐用于用户密码;
- 优先使用Django内置
make_password、check_password、authenticate,不要重复造轮子; - 利用Django原生哈希器特性,实现密码平滑升级;
- 密码哈希只是安全一环,必须配套HTTPS、登录限流、密码复杂度策略。
只要遵循这套方案,不管是Django Admin后台、传统模板登录,还是前后端分离JWT登录,都可以满足企业级安全标准。