0x006-使用email

发送邮件重置密码

需求

  1. 重置密码需要用户输入邮箱,因此需要开发一个输入邮箱的页面
  2. 开发重置密码功能

准备工作

1
2
pip install flask-mail
pip install pyjwt

开发输入邮箱用于重置密码的页面

表单对象

1
2
3
class ResetPasswdByEmailForm(FlaskForm):
email = StringField("请输入你的邮箱:",validators=[DataRequired()])
submit = Submit("提交")

模板页面

1
2
3
4
5
6
7
8
9
10
11
{% extends "_layout.html" %}
{% block main %}
<form method="POST">
{{ form.hidden_tag() }}
<div>
{{ form.email.label }}
{{ form.email() }}
</div>
{{ form.submit() }}
</form>
{% endblock %}

业务逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 重置密码所需要填入email的页面
@app.route('/reset/password',methods=['GET','POST'])
def reset_passwd_by_email():
if current_user.is_authenticated:
flash("已经登录啦")
return redirect(url_for('index'))
form = RestPasswdByEmailForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user:
send_password_reset_email(user)
flash("已经发送邮件,如果没有收到邮件,请检查邮箱地址是否正确。")
return redirect(url_for('login'))
return render_template('reset_passwd_by_email.html',form=form)

开发重置密码功能

准备发送给用户邮件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# templates/email/reset_passwd.txt | reset_passwd.html
<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>
<a href="javascript:alert(0)">Click me to show xss</a>
<script>
alert(1);
</script>
<p>Sincerely,</p>
<p>The Microblog Team</p>

发送邮件的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
1. 首先配置服务器地址,在config.py里面配置
MAIL_SERVER = os.environ.get("MAIL_SERVER")
MAIL_PORT = os.environ.get("MAIL_PORT") or 25
MAIL_USE_TLS = os.environ.get("MAIL_USE_TLS") is not None
MAIL_USERNAME = os.environ.get("MAIL_USERNAME")
MAIL_PASSWORD = os.environ.get("MAIL_PASSWORD")

2. 开发发送邮件的函数, email.py
# 在flask中,有两个状态,一个是请求上下文,一个应用程序上下文
# 很多时候,这两种状态,flask会自动去调配
# 但是如果是自己起的线程,就需要自己去适配。
# 具体还需要学习。
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()

# 发送邮件
def send_password_reset_email(user):
token = user.generate_token()
send_email("【重置你的密码】",sender=app.config['MAIL_USERNAME'],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))

token的功能

token用于我们判断这个用户是否是本人,也就是从这个邮箱里面链接,这个我们在这个用户的类里面直接生成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class User(UserMixix,db.Model):
...
def generate_token(self,expires_in=600):
return jwt.encode(
{"reset_passwd":self.id,"exp":time()+ expires_in},
app.config['SECRET_KEY'],algorithm='HS256').decode('utf-8')

@staticmethod
def validate_token(token):
try:
id = jwt.decode(
token,app.config['SECRET_KEY'],algorithms=['HS256'])['reset_passwd']
except:
app.logger.error("查无此人,此token为{}".format(token))
return
return User.query.get(id)

修改密码的实际页面

  1. 首先判断这个user是不是登录了
  2. 验证一下传过来的token,查询用户,如果是失效的,或者不符合的,直接返回
  3. 开始接受表单数据,写入数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 重置密码的真实页面
@app.route('/reset/password/<token>',methods=['GET','POST'])
def reset_password(token):
if current_user.is_authenticated:
flash("已经登录啦。")
return rediretc(url_for('index'))

user = User.validate_token(token)
if not user:
flash("验证失败,返回首页")
return redirect(url_for('index'))

form = ResetPasswdForm()
if form.validate_on_submit():
passwd = form.password.data
user.set_password(passwd)
try:
db.session.commit()
flash("成功修改密码!!")
except Exception as e:
app.logger.error(e)
db.session.rollback()
flash("修改密码失败!")
return redirect(url_for('reset_passwd'))
return redirect(url_for('login'))
return render_template('reset_passwd.html',form=form)

模板页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{% extends "_layout.html" %}
{% block main %}
<form method="POST">
{{ form.hidden_tag() }}
<div>
{{ form.password.label }}
{{ form.password() }}
</div>
<div>
{{ form.password2.label }}
{{ form.password2() }}
</div>
{{ form.submit() }}
</form>
{% endblock %}