chili 默默学编程

使用邮件重置密码(实现原理及流程)


本文总结自《2017年新版The Flask Mega-Tutorial教程》

原文链接:The-Flask-Mega-Tutorial-zh/docs/第十章:邮件支持.md


本文目录:

1、邮件重置密码的实现原理;

2、详细流程

3,对于发送邮件的异步处理;

详细代码可以见原文。


邮件重置密码的实现原理

1、填写 email;

2、根据 email 找到对应的 user,生成 token;

3、根据 token 信息生成链接,并将该链接发送至邮箱;

4、在邮箱点击链接后,跳转到重置页面;(关键点在此!邮箱中的链接包含了 token 参数所以允许跳转);

5、重置页面更新用户密码;


详细流程

1、login 页面增加一个 “重置密码” 链接;

<a href="{{ url_for('reset_password_request') }}">Click to Reset It</a>

2、点击该链接之后,跳转至一个重置请求页面(reset_password_request.html)

@app.route('/reset_password_request', methods=['GET', 'POST'])
def reset_password_request():
    return render_template('reset_password_request.html', title='Reset Password', form=form)

3、重置请求页面(reset_password_request.html)上只有一个填写注册时邮箱地址的 form

    <form action="" method="post">
        {{ form.hidden_tag() }}
        <p>
            {{ form.email.label }}<br>
            {{ form.email(size=64) }}<br>
            {% for error in form.email.errors %}
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>
        <p>{{ form.submit() }}</p>

4、email 地址填写之后,点击 submit 后发给与步骤 2 相同的路由进行 post 处理

@app.route('/reset_password_request', methods=['GET', 'POST'])
def reset_password_request():
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if user:
            send_password_reset_email(user)
        flash('Check your email for the instructions to reset your password')
        return redirect(url_for('login'))
    return render_template('reset_password_request.html',
                           title='Reset Password', form=form)

处理的步骤有 3 项:

-  根据提交的 email 找到对应的用户;

-  如果填写的 email 正确,则该用户存在,那么就给该用户发送邮件(send_password_reset_email(user));

-  否则,跳转到 login 页面;

5、发送邮件的逻辑(send_password_reset_email(user))

def send_password_reset_email(user):
    token = user.get_reset_password_token()
    send_email('[Microblog] Reset Your Password',
               sender=app.config['ADMINS'][0],
               recipients=[user.email],
               text_body=render_template('email/reset_password.txt',
                                         user=user, token=token),
               html_body=render_template('email/reset_password.html',
                                         user=user, token=token))

其中,send_email 使用的是 flask-smtp 库

send_email 的参数包括:

- title,邮件标题:'[Microblog] Reset Your Password';

- sender,发件人:app.config['ADMINS'][0](前面配置好的);

- recipients,收件人:user 信息中包含的 email 地址;

- text_body 和 html_body:邮件内容(email/reset_password.txt,email/reset_password.html);

另外 token 是 user 对自身 id 的加密令牌,user 能使用自己的 verify_reset_password_token 方法从 token 中解析出 id,并根据 id 查询并返回 user 自身(返回的是 User 对象);

6、邮件的内容(email/reset_password.txt,email/reset_password.html)

txt 和 html 两者内容一样,只是显示的区别,下面只看 htnl 内容:

<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('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>The Microblog Team</p>

中间包含一个链接 url_for('reset_password', token=token, _external=True),点击之后,进入重置页面

7、点击该链接之后,跳转至一个重置页面(reset_password.html)

@app.route('/reset_password/<token>', methods=['GET', 'POST'])
def reset_password(token):
    return render_template('reset_password.html', form=form)

8、重置页面(reset_password.html)上只有一个填写新密码的 form(新密码要去输入两次)

{% extends "base.html" %}

{% block content %}
    <h1>Reset Your Password</h1>
    <form action="" method="post">
        {{ form.hidden_tag() }}
        <p>
            {{ form.password.label }}<br>
            {{ form.password(size=32) }}<br>
            {% for error in form.password.errors %}
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>
        <p>
            {{ form.password2.label }}<br>
            {{ form.password2(size=32) }}<br>
            {% for error in form.password2.errors %}
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>
        <p>{{ form.submit() }}</p>
    </form>
{% endblock %}

9、新密码填写之后,点击 submit 后发给与步骤 7 相同的路由进行 post 处理

@app.route('/reset_password/<token>', methods=['GET', 'POST'])
def reset_password(token):
    if current_user.is_authenticated:
        return redirect(url_for('index'))
    user = User.verify_reset_password_token(token)
    if not user:
        return redirect(url_for('index'))
    form = ResetPasswordForm()
    if form.validate_on_submit():
        user.set_password(form.password.data)
        db.session.commit()
        flash('Your password has been reset.')
        return redirect(url_for('login'))
    return render_template('reset_password.html', form=form)

处理的步骤有 3 项:

 -  根据 token 解密出一个 user 对象;(超过有效期或者错误时,返回 None);

 -  如果能能解密出一个 user 对象,则将新密码更新至 user 对象中,,最后跳转到 login 页面;

- 否则跳转到 index 页面;


对于发送邮件的异步处理

from threading import Thread
# ...

def send_async_email(app, msg):
    with app.app_context():
        mail.send(msg)

def send_email(subject, sender, recipients, text_body, html_body):
    msg = Message(subject, sender=sender, recipients=recipients)
    msg.body = text_body
    msg.html = html_body
    Thread(target=send_async_email, args=(app, msg)).start()

reply ( 4 )

winshu@2018-10-20 10:38:32

我能把你在豆瓣的轨迹收集起来,路飞,哈哈

xiaokong@2018-10-20 18:52:21

@winshu 我的天,你是谁,你从哪里来

winshu@2018-10-22 10:25:43

八十万禁军教头

xiaokong@2018-10-22 13:51:24

哦哦,程序猿不得了