1. flask-mail 다운로드

$ pip install flask-mail

이메일 전송을 위해 많이 쓰이는 flask-mail을 설치합니다.

 

 

 

2. pyjwt 다운로드

$ pip install pyjwt

비밀번호 재설정 링크에는 보안토큰이 있는데, 이런 토큰을 생성하기 위해 널리 사용되는, python 패키지가 있는 JSON Web Tokens를 사용합니다.

그렇습니다. 비밀번호 재설정이 가능한 링크를 이메일로 보내주는 기능을 추가할 것입니다.

 

 

 

3. Flask-Mail 인스턴스 생성

# vim app/__init__.py

# ...
from flask_mail import Mail

# Flask-Mail 인스턴스
app = Flask(__name__)

# ...

mail = Mail(app)

 

 

 

4. 이메일 전송 래퍼 기능

# vim app/email.py

from flask_mail import Message
from app import mail

def send_mail(subject, sender, recipients, text_body, html_body):
    msg = Message(subject, sender=sender, recipients=recipients)        # 메일 세팅
    msg.body = text_body        # text body
    msg.html = html_body        # html body
    mail.send(msg)      # 메일 발송

 

 

 

5. 비밀번호 재설정 링크 활성화

<!-- vim app/templates/login/html -->

<!-- ... -->

    <p>
        Forgot Your Password?
        <a href="{{ url_for('reset_password_request') }}">Click to Reset It</a>
    </p>

<!-- ... -->

 

 

 

6. 비밀번호 재설정 양식 추가

# vim app/forms.py

# 비밀번호 요청 양식 재설정
class ResetPasswordRequestForm(FlaskForm):
    email = StringField('Email', validators=[DataRequired(), Email()])
    submit = SubmitField('Request Password Reset')

 

 

 

7. 비밀번호 재설정 템플릿 생성

<!-- vim app/templates/reset_password_request.html -->

{% extends "base.html" %}

{% block content %}
    <h1>Reset Password</h1>
    <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>
    </form>
{% endblock %}

 

 

 

8. 비밀번호 재설정 요청 보기 기능 추가

# vim app/routes.py

# 비밀번호 재설정 요청 보기 기능
from app.forms import ResetPasswordRequestForm
from app.email import send_password_reset_email

@app.route('/reset_password_request', methods=['GET', 'POST'])
def reset_password_request():
    if current_user.is_authenticated:
        return redirect(url_for('index'))
    form = ResetPasswordRequestForm()
    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)

사용자가 로그인 되있는 경우, 비밀번호 재설정이 필요하지 않기 때문에 index로 보내줌

사용자가 제출한 이메일로 비밀번호 재설정 페이지 링크를 보내주고, 현재 페이지는 로그인 페이지로 바뀜

 

 

 

9. 비밀번호 토큰 메소드 재설정

# vim app/models.py

class User(UserMixin, db.Model):
    # ...

    # 비밀번호 토큰 메소드를 재설정
    def get_reset_password_token(self, expires_in=600):
        # jwt.encode() 함수로 바이트 토큰을 반환하지만, decode('utf-8')을 통해 문자열로 토큰을 가짐
        return jwt.encode(
            {'reset_password': self.id, 'exp': time() + expires_in},
            app.config['SECRET_KEY'], algorithm='HS256').decode('utf-8')
    # 정적 메소드는 클래스 메소드와 유사하지만, 클래스를 첫 번째 인수로 받지 않는 특징이 있다.
    @staticmethod
    def verify_reset_password_token(token):
        # 토큰의 유효성을 검사할 수 없거나 만료되면 발생되는 오류를 예외처피하여 None으로 반환함
        try:
            id = jwt.decode(token, app.config['SECRET_KEY'],
                            algorithms=['HS256'])['reset_password']
        except:
            return
        return User.query.get(id)

 

 

 

10. 토큰을 활용해 비밀번호 재설정 이메일 생성

# vim app/email.py

from flask import render_template
from app import app

# ...

# 비밀번호 재설정 이메일 보내기 기능
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))

이메일의 텍스트 및 HTML 내용이 익숙한 render_template() 기능을 사용하여 템플릿에서 생성됨

템플릿은 사용자와 토큰을 인수로 받아 개인화된 이메일 메시지를 생성할 수 있음

 

 

 

11. 비밀번호 재설정 이메일

$ mkdir app/templates/email

 < 텍스트 버전 >

$ vim app/templates/email/reset_password.txt

Dear {{ user.username }},

To reset your password click on the following link:

{{ url_for('reset_password', token=token, _external=True) }}

If you have not requested a password reset simply ignore this message.

Sincerely,

The Microblog Team

 < HTML 버전 >

$ vim app/templates/email/reset_password.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>
<p>Sincerely,</p>
<p>The Microblog Team</p>

 

 

 

12. 비밀번호 재설정 보기 기능

사용자가 이메일 링크를 클릭하면 이 기능과 관련된 두 번째 경로가 트리거됩니다.

# vim app/routes.py

# 비밀번호 재설정 보기 기능
from app.forms import ResetPasswordForm
@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)

사용자가 로그인 상태이면 index로 리다이렉트합니다.

User 클래스에서 토큰 확인 방법을 호출함으로서 사용자가 누구인지 확인합니다.

토큰이 유효하지 않으면 index로 리다이렉트합니다.

토큰이 유효하면 새 비밀번호를 요청하는 두 번째 양식을 사용자에게 제시합니다. 비밀번호를 변경하는 set_password() 방법을 호출한 후 사용자가 로그인할 수 있는 로그인 페이지로 리다이렉션합니다.

 

 

 

13. 비밀번호 재설정

양식

# vim app/forms.py

class ResetPasswordForm(FlaskForm):
    password = PasswordField('Password', validators=[DataRequired()])
    password2 = PasswordField(
        'Repeat Password', validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('Request Password Reset')

HTML 템플릿

<!-- vim app/templates/reset_password.html -->

{% 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 %}

 

 

 

14. 기능 완성되었으므로 TEST 진행

 

했는데 잘 안되는 부분이 나오네요... 원인을 못찾고 있습니다.

 

smtplib.SMTPServerDisconnected

smtplib.SMTPServerDisconnected: please run connect() first

 

 

 

 

 

 

 

 

 

 

 

 

 

 

+ Recent posts