现在你的博客有个大漏洞:任何人都能发文章、删文章。
今天装上门禁——注册、登录、登出,顺便回答【Python项目实战】项目·强密码生成器:随机、安全与剪贴板这篇文章里埋的那个问题:为什么密码必须“哈希加盐”存储。
🎯本篇成品:博客v3——注册、登录、登出、flash提示、登录门禁(没登录不能写/删)。
零新增依赖:密码哈希用的werkzeug随Flask自带。含注释约130行。
📌 太长不看版(给想快速上手的你)
| 项目信息 | 一句话说明 |
|---|---|
| 项目名称 | Flask博客(v3·用户系统版) |
| 代码行数 | ~130行(含注释) + 模板文件 |
| 依赖 | 零新增!werkzeug随Flask自带 |
| 核心功能 | 注册 + 登录 + 登出 + 登录门禁 + flash提示 |
| 跑起来的命令 | python main.py→ 浏览器打开http://127.0.0.1:5000 |
| 核心知识点 | 密码哈希加盐、session会话、自定义装饰器 |
| 做完你能得到 | 一个有门禁的博客,从“玩具”变“产品”! |
一、密码为什么不能明文存(第9篇伏笔兑现)
假设数据库泄露了(忘改debug、弱密码后台、云盘误分享……),两种结局:
| 存储方式 | 攻击者看到的 | 后果 |
|---|---|---|
| ❌明文存密码 | 打开表,所有账号当场沦陷 | 大多数人到处复用密码,沦陷会蔓延到邮箱、支付账号 |
| ✅哈希存 | 一串$scrypt$32768$8$1$...的天书 | 没有密码原文,登录不了 |
🔪 哈希函数是单向的碎纸机
📌把密码绞成指纹容易,从指纹还原密码(目前)不可能。
登录时做的不是“解密密码”,而是把你输入的密码重新绞一遍,比对两坨指纹是否一致。
🧂 “加盐”是什么?
| 问题 | 加盐的解法 |
|---|---|
| 如果两个用户密码相同,哈希值也相同 | 盐 = 给每个密码撒一把随机字符再哈希 |
| 攻击者提前算好“常见密码→哈希”对照表(彩虹表),按表查就破解了 | 让相同的密码生出不同的哈希,彩虹表直接作废 |
🧪 亲手验证
💡
secrets是“必须不可预测”,werkzeug的盐是“故意每次不同”——方向相反的一对。
>>>fromwerkzeug.securityimportgenerate_password_hash>>>generate_password_hash("python666")'scrypt:32768:8:1$saltA$...'>>>generate_password_hash("python666")'scrypt:32768:8:1$saltB$...'# 同一个密码,两个完全不同的哈希✅好消息:这两个函数在
werkzeug.security里,Flask自带werkzeug——本篇零新增依赖。
二、第二个概念:session——HTTP的“记忆手环”
HTTP是无状态的:每个请求都是陌生人,上一秒登录、下一秒服务器就把你忘了。
session就是解决方案:登录成功后服务器发你一个“签名手环”(Cookie),之后每个请求亮手环,Flask验签后知道“你是谁”。
| 概念 | 比喻 | 说明 |
|---|---|---|
session["uid"] = 用户id | 手环内容 | 由Flask用SECRET_KEY签名,篡改内容签名就对不上,手环自动作废 |
SECRET_KEY | 手环的防伪印刷术 | 泄露它,任何人都能伪造登录 |
三、第0步:setup
1. 复制flask_blog_v2为flask_blog_v3(保留v2) 2. 激活venv 3. 无需安装任何新包(werkzeug随Flask自带) 4. git commit一次干净的起点四、第1步:SECRET_KEY——手环的防伪印刷术
importos app=Flask(__name__)app.config["SQLALCHEMY_DATABASE_URI"]="sqlite:///blog.db"app.config["SQLALCHEMY_TRACK_MODIFICATIONS"]=False# 生产环境:从环境变量读(第5篇军规——密钥不进仓库)app.config["SECRET_KEY"]=os.environ.get("BLOG_SECRET_KEY","dev-only-do-not-use-in-prod")⚠️ 两个注意点
| # | 注意点 | 说明 |
|---|---|---|
| ① | 没有SECRET_KEY就用session | Flask会当场报错(第十节①) |
| ② | SECRET_KEY必须固定 | 每次启动随机生成的话,重启一次全体用户被“踢下线”(第十节③) |
五、第2步:User模型——存指纹,不存密码
fromwerkzeug.securityimportgenerate_password_hash,check_password_hashclassUser(db.Model):__tablename__="users"id=db.Column(db.Integer,primary_key=True)username=db.Column(db.String(40),unique=True,nullable=False)password_hash=db.Column