Flask+Bootstrap实战四
书接上回,已经完成了用户的注册,登入登出界面,以及重置密码的界面,下一步首先要实现的是发送重置密码验证邮件的功能
2025-03-09 20:49:09 星期日
项目教程地址:
Youtobe源地址:Bootstrap Flask
Bilibili搬运:哔哩哔哩
一.使用pyjwt对用户密码进行数据加密
这么做的目的是为了防止直接将密码发送到个人邮箱会导致的数据不安全问题,这里是pyjwt的教程文档链接
修改models.py中的用户模型
这里注意,需要在用户模型下的User类中添加如下两个函数,分别用来生成加密后的id,以及解密后在数据库中找到的id
注意,这里config的时候不是圆括号“()”,是方括号“[]”
def generate_reset_password_token(self):
return jwt.encode({"id": self.id}, current_app.config["SECRET_KEY"], algorithm="HS256")
def check_reset_password_token(self, token):
try:
data = jwt.decode(token, current_app.config["SECRET_KEY"], algorithms=["HS256"])
return User.query.filter_by(id = data["id"]).first()
except:
return
这里注意他的写法
二.验证邮件发送
需要pip安装flask_mail,之后进行config的配置
- 修改__init__.py文件,做出如下修改
mail = Mail(app)
- 修改config.py文件
MAIL_SERVER = 'smtp.163.com'
MAIL_PORT = 465
MAIL_USE_SSL = True
MAIL_USERNAME = os.environ.get('EMAIL_KEY') or '邮箱'
MAIL_PASSWORD = os.environ.get('EMAIL_PASSWORD') or '授权码'
- 修改routes.py中send_password_reset_request的路由,这里需要编写一个新的函数用来发送邮件
send_reset_password_token(user, token),其有两个参数,一个是user,一个是token
@app.route('/send_password_reset_request', methods=['GET', 'POST'])
def send_password_reset_request():
form = PasswordResetForm()
if form.validate_on_submit():
email = form.email.data
user = User.query.filter_by(email=email).first()
token = user.generate_reset_password_token()
send_reset_password_token(user, token)
flash("Email is sent, please check your emailbox!", category="info")
return render_template("send_password_reset_request.html", form=form)
- 新建一个reset_mail.py文件编写send_reset_password_token函数
from flask_mail import Message
from flask import current_app, render_template
def send_reset_password_token(user, token):
msg = Message("[Flask App] Reset your password",
sender=current_app.config["MAIL_USERNAME"],
recipients=[user.email],
html=render_template("reset_password_mail.html", token=token))
注意,这里是需要返回一个特殊的html模板,将他命名为reset_password_mail.html他作为一个中间跳板,会是打开发送的链接邮件之后,跳转到一个新的用来更改密码的界面,reset_password.html
- 首先需要编写reset_password_mail.html模板,这个在alphafan的github有现成的,可以直接拿来用。链接地址
<p>Dear {{ user.username }},</p>
<p>
To reset your password
<a href="{{ url_for('reset_password', token=token, _external=True) }}">
click here
</a>.
</p>
<p>Alternatively, you can paste the following link in your browser's address bar:</p>
<p>{{ url_for('auth.reset_password', token=token, _external=True) }}</p>
<p>If you have not requested a password reset simply ignore this message.</p>
<p>Sincerely,</p>
<p>Flask App</p>
- 之后下一步就是需要编写reset_password.html了,内容和register大差不差
{% extends "base.html" %}
{% block app_content %}
<h1>Reset Now</h1>
<br>
<div class="row">
<div class="col-md-6">
{% import 'bootstrap/wtf.html' as wtf %}
{{ wtf.quick_form(form) }}
</div>
</div>
{% endblock %}
- 之后我们就需要有一个reset_password的路由函数,以及需要编写重置密码的form表单了。首先新建一个路由函数
@app.route('/reset_password', methods=['GET', 'POST'])
def reset_password():
if current_user.is_authenticated:
return redirect(url_for("index"))
form = PasswordResetForm()
return render_template("reset_password.html", form=form)
- 在form.py中新建一个PasswordResetForm
class PasswordResetForm(FlaskForm):
password = PasswordField("Password", validators=[DataRequired(), Length(min=6, max=20)])
conform = StringField("Repeat Password", validators=[DataRequired(), EqualTo("password")])
submit = SubmitField("Register")
- 遇到一个小错误
这个意思是说邮箱的授权码和我们登录邮箱的密码是不一样的,需要先获取一个授权码。
点开页面进行跳转之后就会进入到密码重置界面,大功告成
三.完成密码的修改和重置
- 首先修改routes.py中的路由函数。
@app.route('/reset_password/<token>', methods=['GET', 'POST'])
def reset_password(token):
if current_user.is_authenticated:
return redirect(url_for("index"))
form = PasswordResetForm()
if form.validate_on_submit():
user = User.check_reset_password_token(token)
if user:
user.password = bcrypt.generate_password_hash(form.password.data)
db.session.commit()
flash("Your password reset is done!", category="info")
return redirect(url_for("login"))
else:
flash("User not exists, please register!", category="info")
return redirect(url_for("login"))
return render_template("reset_password.html", form=form)
这里首先解释一下为什么该链接能够完成用户的确认,因为在这里定义了一个user去调用用户模型下的check_reset_password_token函数,该函数会在数据库中进行id查找(token加密的就是id),找到了就会返回user,是一个true。
另外需要注意的是,这里的'/reset_password/
- 回到models.py中修改,这里需要的就是静态方法@staticmethod,在routes中函数调用的时候只需要一个token参数,如果不加这个方法,就会报下面的错
@staticmethod
def check_reset_password_token(token):
try:
data = jwt.decode(token, current_app.config["SECRET_KEY"], algorithms=["HS256"])
return User.query.filter_by(id = data["id"]).first()
except:
return
此时可以正常更新数据库中的密码内容
四.使用多线程加速邮件发送
-
首先在reset_mail.py中修改,导入
from threading import Thread,还需要从from app import mail, app -
新定义一个异步函数,用来发送邮件
def send_async_mail(app, msg):
with app.current_app:
mail.send(msg)
- 将原先的
send_reset_password_token函数进行修改
def send_reset_password_token(user, token):
msg = Message("[Flask App] Reset your password",
sender=current_app.config["MAIL_USERNAME"],
recipients=[user.email],
html=render_template("reset_password_mail.html", user=user, token=token))
# mail.send(msg)
Thread(target=send_async_mail, args=(app, msg,)).start()
在每次调用时候,都会加载一个新的线程来启动上面的邮件发送函数,这样能及时响应前端,就不用再转圈圈了,就会直接弹出消息如下
浙公网安备 33010602011771号