#找回密码逻辑:集成 Flask-Mail 发送验证邮件
📂 所属阶段:第三阶段 — 用户系统(安全篇)
🔗 相关章节:Flask-Login 实战 · 密码安全加密
#1. 找回密码流程
忘记密码? → 输入邮箱 → 发送重置链接 → 邮件含 token → 点击 token 页面 → 设置新密码#2. 安装 Flask-Mail
pip install flask-mail#3. 配置
# app/extensions.py
from flask_mail import Mail
mail = Mail()
# app/__init__.py
def create_app():
app = Flask(__name__)
app.config["MAIL_SERVER"] = "smtp.gmail.com"
app.config["MAIL_PORT"] = 587
app.config["MAIL_USE_TLS"] = True
app.config["MAIL_USERNAME"] = os.getenv("MAIL_USERNAME")
app.config["MAIL_PASSWORD"] = os.getenv("MAIL_PASSWORD")
app.config["MAIL_DEFAULT_SENDER"] = ("道满博客", os.getenv("MAIL_USERNAME"))
mail.init_app(app)#4. Token 生成与验证
#4.1 使用 itsdangerous 生成 Token
from itsdangerous import URLSafeTimedSerializer
class TokenManager:
def __init__(self, secret_key):
self.serializer = URLSafeTimedSerializer(secret_key)
def generate_token(self, email):
"""生成密码重置 Token"""
return self.serializer.dumps(email, salt="password-reset-salt")
def verify_token(self, token, expiration=3600):
"""验证 Token(默认 1 小时有效)"""
try:
email = self.serializer.loads(
token,
salt="password-reset-salt",
max_age=expiration
)
return email
except Exception:
return None
token_manager = TokenManager(app.config["SECRET_KEY"])#4.2 发送重置邮件
from flask_mail import Message
@auth_bp.route("/forgot-password", methods=["GET", "POST"])
def forgot_password():
form = ForgotPasswordForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user:
# 生成 Token
token = token_manager.generate_token(user.email)
# 构造重置链接
reset_url = url_for("auth.reset_password", token=token, _external=True)
# 发送邮件
msg = Message(
subject="【道满博客】密码重置请求",
recipients=[user.email],
body=f"""
您好,{user.username or user.email}:
我们收到了您对 {user.email} 账户的密码重置请求。
请在 1 小时内点击以下链接重置密码:
{reset_url}
如果这不是您本人的操作,请忽略此邮件,您的账户安全不受影响。
—— 道满博客团队
"""
)
mail.send(msg)
# 无论用户存不存在都显示成功(防止枚举攻击)
flash("如果该邮箱已注册,我们已发送重置链接到您的邮箱。", "info")
return redirect(url_for("auth.login"))
return render_template("auth/forgot_password.html", form=form)#4.3 重置密码页面
@auth_bp.route("/reset-password/<token>", methods=["GET", "POST"])
def reset_password(token):
# 验证 Token
email = token_manager.verify_token(token)
if not email:
flash("重置链接已过期或无效,请重新申请。", "danger")
return redirect(url_for("auth.forgot_password"))
user = User.query.filter_by(email=email).first()
if not user:
flash("用户不存在。", "danger")
return redirect(url_for("auth.register"))
form = ResetPasswordForm()
if form.validate_on_submit():
user.password = form.password.data # 自动哈希
db.session.commit()
flash("密码已重置,请使用新密码登录!", "success")
return redirect(url_for("auth.login"))
return render_template("auth/reset_password.html", form=form, token=token)#5. 安全注意事项
✅ 安全实践
1. Token 设置过期时间(1 小时内有效)
2. 无论用户是否存在都显示同样信息(防枚举)
3. 重置后使旧 Token 失效
4. 记录密码重置日志
5. 发送重置邮件前验证用户是否存在
❌ 避免
1. 在邮件中直接发送明文新密码
2. Token 无过期时间
3. 验证失败时暴露用户是否存在#6. 小结
# 找回密码四步:
# 1. 生成 Token
token = generate_token(email)
# 2. 发送邮件
msg = Message(subject="...", recipients=[email], body="...")
mail.send(msg)
# 3. 验证 Token
email = verify_token(token) # 过期返回 None
# 4. 重置密码
user.password = new_password # 自动哈希
db.session.commit()💡 提示:生产环境推荐使用 SendGrid、Mailgun 等专业邮件服务,自建 SMTP 在发送量和送达率上不如专业服务。
🔗 扩展阅读

