Calender Website

[Flask] Flask-login을 사용한 로그인 시스템 구현

코딩하기싫어근데취직은해야함 2025. 1. 16. 03:12

<HTML>

기본적인 템플릿은 이전에 만들었던 register-form을 참고하였다. 

    <div class="form-container">
        <form id="login-form" class="login-form" method="post">
            <div class="form-group">
                <label for="user-email">이메일</label>
                <input type="email" id="user-email" class="login-input" name="user-email" placeholder="Email" required>
            </div>
            <div class="form-group">
                <label for="user-pwd">비밀번호</label>
                <input type="password" id="user-pwd" class="login-input" name="user-pwd" placeholder="Password" required>
            </div>
            <div class="button-div">
                <button type="submit">로그인</button>
            </div>
        </form>
    </div>

 

<Flask>

1. flask-login 라이브러리를 가상머신 활성화 후 설치

source venv/Scripts/activate                  # 가상머신 활성화
pip install flask-login                       # flask-login 설치
pip list --format=freeze > requirements.txt   # 설치된 라이브러리 requirements.txt에 저장 (배포용)
deactivate                                    # 가상머신 비활성화

 

2. app/__init__.py

LoginManager()객체를 불러와 초기화시키고, load_user 콜백함수를 등록한다. 

  • load_user(user_id)의 핵심 역할은 사용자 ID로 데이터베이스에서 사용자 객체를 검색하여 반환하는 것이다.
  • Flask-Login은 이 함수에서 반환된 객체를 기반으로 current_user를 설정한다. 이 객체를 템플릿에 넘겨주면 해당 페이지에서 사용자 정보를 열람, 수정할 수 있다.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from config import Config
from flask_login import LoginManager

db = SQLAlchemy()
login_manager = LoginManager()

def create_app():
    app = Flask(__name__)
    app.config.from_object(Config)
    
    # 데이터 베이스 연결
    db.init_app(app)

    # 로그인 관리 
    login_manager.init_app(app)

    # load_user 콜백 함수
    from app.models import User

    @login_manager.user_loader
    def load_user(user_id):
        return User.query.get(int(user_id))

    # 블루프린트 연결
    from app.routes.views import views
    from app.routes.api import api

    app.register_blueprint(views)
    app.register_blueprint(api)
    
    return app

 

+ 추가

login_required에서 로그인이 되어있지 않으면 welcome 페이지로 넘어갈 수 있도록 해준다.

이제 profile_setting과 같은 페이지를 로그인이 되지 않은 상태로 접근할 수 없다.

# login_requried에서 로그인이 되어 있지 않을 경우 핸들링
@login_manager.unauthorized_handler
def unauthorized_callback():
    return redirect("/welcome")

 

3. models/model.py

데이터베이스와 연결할 User 모델을 수정한다. UserMixin은 Flask-login에서 요구하는 사용자 속성과 기능을 제공하는 역할을 하며 is_authenticated(), is_active(), is_anonymous(), get_id() 등 Flask-Login이 필요로 하는 메서드들을 자동으로 구현한다.

from app import db
from datetime import datetime
from flask_login import UserMixin

class User(UserMixin, db.Model):
    __tablename__ = 'users'
    
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(200), nullable=False)
    phone = db.Column(db.String(20))
    birth = db.Column(db.Date)
    interest = db.Column(db.String(200))
    work = db.Column(db.String(100))
    location = db.Column(db.String(100))
    image_path = db.Column(db.String(200))
    website = db.Column(db.String(200))
    description = db.Column(db.String(500))

 

4. routes/api.py

이제 로그인 기능을 구현한다. HTML에서 이메일, 비밀번호를 받아와 email이 동일한 유저를 찾는다.

- 유저 o, 비밀번호 일치 o : 로그인 성공, 메인 페이지로 이동

- 유저 o, 비밀번호 일지 x : 비밀번호 불일치, 페이지 이동 x

- 유저 x                             :  이메일 불일치, 페이지 이동 x

 

*****

만약 HTML에서 form actione='/login'으로 하여 바로 Flask와 통신하면 비동기 통신이 아닌 동기 방식으로 이루어져 redirect, render_template과 같이 페이지를 새로고침해야 한다.

HTML -> Javascript -> Flask -> Javascript 처럼 비동기 통신인 AJAX 통신을 사용하면 페이지를 새로 고침하지 않아도 되어 로그인 실패 알람과 같은 기능을 구현하기 좋다.

*****

@api.route('/login', methods=['POST', 'GET'])
def login():
    if request.method == 'POST':
        email = request.form.get('user-email')
        password = request.form.get('user-pwd')
        user = User.query.filter_by(email=email).first()
        if user:
            if check_password_hash(user.password, password):
                login_user(user)
                return jsonify({
                    'flag' : True,
                    'message' : 'Timely에 오신걸 환영합니다!',
                })
            else:
                return jsonify({
                    'flag' : False,
                    'message' : '비밀번호를 다시 확인해주세요.',
                })
        else:
            return jsonify({
                'flag' : False,
                'message' : '이메일을 다시 확인해주세요.',
            })
    return render_template('sign_in.html')

 

<Javascript>

로그인 결과 알람을 위하여 Toastify를 사용했다. 로그인 성공 시 알람을 띄우고 프로필 세팅 페이지로 이동하고, 로그인 실패 시 알람을 띄우고 해당 페이지를 그대로 유지한다. 

    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css">
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/toastify-js"></script>
document.addEventListener('DOMContentLoaded', function() {
    document.querySelector('.login-form').addEventListener('submit', function(e) {
        e.preventDefault();
        const formData = new FormData(this);

        axios.post('/login', formData)
        .then(function(response) {
            if (response.data.flag) {
                Toastify({
                    text: response.data.message,
                    duration: 1000,
                    gravity: "top",
                    position: "top",
                    style: {
                        background: "black",
                    }
                }).showToast();
                
                setTimeout(() => {
                    window.location.href = '/main';
                }, 1000);
            }
            else {
                Toastify({
                    text: response.data.message,
                    duration: 2000,
                    gravity: "top",
                    position: "top",
                    style: {
                        background: "black",
                    }
                }).showToast();
            }
        })
        .catch(function(error) {
            console.error('Error', error)
        });
    });
});

 


로그인 흐름

1. flask-login을 이용하여 로그인하면 세션에 사용자의 아이디가 저장되어 session['user_id'] = user_id와 같이 관리된다.

2. 클라이언트가 서버에 요청할 때, flask-login을 session['user_id']를 이용해 load_user 콜백 함수를 호출한다.

3. load_user는 user_id를 검색해 데이터베이스에서 User 객체를 반환한다.

4. 반환된 User 객체는 current_user로 설정된다. 

5. current_user를 템플릿에 넘기면 해당 페이지에서 사용자의 정보를 열람할 수 있다.

1. 'app/routes/views.py'

메인 페이지는 로그인된 유저만 접근 가능하도록 @login_required 데코레이터를 붙여 접속하기전 로그인이 되었는가 확인하고 user=current_user로 현재 로그인된 유저를 함꼐 넘겨 준다.

@views.route('/main')
@login_required
def main():
    return render_template('calender_v2.html', user=current_user)

 

2. calender_v2.html

Jinja2 템플릿을 활용하여 사용자 정보를 표시할 수 있다. 만약 프로필 이미지가 저장되어 있지 않으면 기본 프로필을 적용할 수도 있다.

        <div class="user-info">
            <img src="{{ url_for('static', filename='images/profile/woman.png' if user.image_path == ''
                else user.image_path) }}" class="profile-picture">
            <div>
                <p class="user-name">{{ user.username }}</p>
                <p class="description">{{ user.description }}</p>
            </div>
        </div>

 


결과 화면

로그인 페이지

이메일 불일치

비밀번호 불일치

로그인 성공 

메인 페이지 프로필 (프로필 x / 프로필 o)