1. 비밀번호 해싱 및 확인 함수 만들기
# vim app/models.py
from werkzeug.security import generate_password_hash, check_password_hash
# ...
class User(db.Model):
# ...
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
2. 명령 프롬프트에서 테스트해보기
from app import db
from app.models import User, Post
u = User(username='susan', email='susan@example.com')
u.set_password('mypassword')
u.check_password('anotherpassword')
# 출력값 : False
u.check_password('mypassword')
# 출력값 : True
3. 플라스크 로그인 설치
$ pip install flask-login
4. 플라스크 로그인 초기화
# vim app/__init__.py
# ...
from flask_login import LoginManager
app = Flask(__name__)
# ...
login = LoginManager(app)
# ...
5. 플라스크 로그인 사용자 믹스 인 클래스
# vim app/models.py
# ...
from flask_login import UserMixin
class User(UserMixin, db.Model):
# ...
- is_authenticated
인증된 경우 True, 그렇지 않은 경우 False
- is_active
계정이 활성화된 경우 True, 그렇지 않은 경우 False
- is_anonymous
익명 사용자는 True, 그렇지 않은 경우 False
- get_id()
사용자의 고유 식별 문자를 보여주는 메소드
=> 이 4가지의 구현이 일반적이므로 Flask-Login은 이에 적합한 사용자 모델 클래스 구현을 지원하는 믹스 인 클래스를 제공한다.
6. 플라스크 로그인 사용자 로더 기능
# vim app/models.py
from app import login
# ...
@login.user_loader
def load_user(id):
return User.query.get(int(id))
7. 로그인 페이지 구현
# vim app/routes.py
# ...
from flask_login import current_user, login_user
from app.models import User
# ...
@app.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('index'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is None or not user.check_password(form.password.data):
flash('Invalid username or password')
return redirect(url_for('login'))
login_user(user, remember=form.remember_me.data)
return redirect(url_for('index'))
return render_template('login.html', title='Sign In', form=form)
기존의 def login은 지우고, DB에 엑세스하고 비밀번호 해시를 생성, 확인하는 형태의 로그인 기능을 구현해봅니다.
8. 로그아웃 페이지 구현
# vim app/routes.py
# ...
from flask_login import logout_user
# ...
@app.route('/logout')
def logout():
logout_user()
return redirect(url_for('index'))
<!-- vim app/templates/base.html -->
<!-- ... -->
<div>
Microblog:
<a href="{{ url_for('index') }}">Home</a>
{% if current_user.is_anonymous %}
<a href="{{ url_for('login') }}">Login</a>
{% else %}
<a href="{{ url_for('logout') }}">Logout</a>
{% endif %}
</div>
<!-- ... -->
9. Flask-Login에서 로그인 처리하는 보기 기능
# vim app/__init__.py
# ...
login = LoginManager(app)
login.login_view = 'login'
# routes와 데이터베이스 구조 정의하는 models 호출
from app import routes, models
위 우항의 'login'은 로그인 보기의 함수(엔드포인트) 이름이다. 즉, url_for() URL 호출을 위한 것입니다.
10. 데코레이터
인증되지 않은 사용자의 액세스를 허용하지 않는, 로그인이 필요한 페이지로 설정하는 것입니다.
아래와 같이 설정시, /index 접근 유저는 인증된 유저여야만한다는 의미입니다.
# vim app/routes.py
from flask_login import login_required
@app.route('/')
@app.route('/index')
@login_required
def index():
# ...
11. "next" 페이지로 Redirection
# vim app/routes.py
# ...
from flask import request
from werkzeug.urls import url_parse
@app.route('/login', methods=['GET', 'POST'])
def login():
# ...
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is None or not user.check_password(form.password.data):
flash('Invalid username or password')
return redirect(url_for('login'))
login_user(user, remember=form.remember_me.data)
next_page = request.args.get('next')
if not next_page or url_parse(next_page).netloc != '':
next_page = url_for('index')
return redirect(next_page)
# ...
인증되지 않은 사용자가 login 페이지가 아닌 다른 페이지로 접속시 해당 링크를 next에 두고, login 페이지로 보냅니다.
이후 로그인에 성공하면, 잘못 접근했으나 사용자가 보기를 원한 페이지 링크 next로 이동시켜줍니다.
12. 현재 사용자를 템플릿으로 전달
user.username을 current_user.username 으로 바꿔줍니다.
<!-- vim app/templates/index.html -->
{% extends "base.html" %}
{% block content %}
<h1>Hi, {{ current_user.username }}!</h1>
{% for post in posts %}
<div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
{% endfor %}
{% endblock %}
13. 회원가입 기능
# vim app/forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
from app.models import User
# ...
# 회원가입 폼
class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
# 비밀번호는 오타 방지를 위해 두 번 입력하도록 하고, 일치하는지 확인
password2 = PasswordField(
'Repeat Password', validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Register')
# 이미 있는 사용자명일 경우 사용 불가
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user is not None:
raise ValidationError('Please use a different username.')
# 이미 있는 이메일일 경우 사용 불가
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user is not None:
raise ValidationError('Please use a different email address.')
<!-- vim app/templates/register.html -->
{% extends "base.html" %}
{% block content %}
<h1>Register</h1>
<form action="" method="post">
{{ form.hidden_tag() }}
<p>
{{ form.username.label }}<br>
{{ form.username(size=32) }}<br>
{% for error in form.username.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<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.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 %}
<!-- vim app/templates/login.html -->
<!-- ... -->
<p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
<p>{{ form.submit() }}</p>
<p>New User? <a href="{{ url_for('register') }}">Click to Register!</a></p>
</form>
{% endblock %}
# vim app/routes.py
from app import db
from app.forms import RegistrationForm
# ...
@app.route('/register', methods=['GET', 'POST'])
def register():
# 로그인되어있는 경우 index로 리다이렉트 시킴
if current_user.is_authenticated:
return redirect(url_for('index'))
form = RegistrationForm() # 회원가입 폼 불러오기
# 정상적인 입력값으로 확인되면 회원정보 DB에 정보 추가하기
if form.validate_on_submit():
user = User(username=form.username.data, email=form.email.data)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
flash('Congratulations, you are now a registered user!')
return redirect(url_for('login'))
return render_template('register.html', title='Register', form=form)
14. 서버 열어서 직접 사용해보기
$ flask run --host 0.0.0.0 --port 5001