[Flask-Login] Flask-Login 이용하여 로그인 기능 구현하기

유콩·2021년 9월 12일
3

간단하게 "flask 에서 로그인 기능을 도와주는 라이브러리 flask_login 이 있다", "flask_login 라이브러리를 이용하면 사용자 정보를 세션에 저장한다." 정도만 알고있었다. 실제로 구현해보려니 주로 사용한다는 라이브러리치고는 한글로 된 자료가 적어서 정리해보았다.

- flask_login version : 0.5.0
- 현재 개발중인 WE 프로젝트 소스코드 사용

📑 Flask-Login 라이브러리 설치

pip install flask-login

📑 Flask-Login 개요

Flask 프레임워크로 개발한 웹 어플리케이션의 로그인 기능을 쉽게 구현할 수 있도록 도와주는 라이브러리이다. 필수로 사용해야 하는 것은 아니며 사용자 정보 세션 관리를 쉽게 도와주는 용도로 사용한다.

📑 사용자 클래스 생성

flask_login에서 사용할 클래스 객체를 생성한다. 일반 모델 객체 생성하듯이 똑같이 생성한다. 사용자 정보에 저장하고 싶은 값을 기준으로 만든다. 나는 user_id만 필요하기 때문에 user_id만 포함하였다.

app/model/common/model_user.py

# flask_login에서 제공하는 사용자 클래스 객체
from flask_login import UserMixin

# DB 연결 정보가 저장되어 있는 config
from app.config import DB

# 쿼리문 실행 함수
from app.model.common.model_db_connect import select, insert, update, delete

# UserMixin 상속하여 flask_login에서 제공하는 기본 함수들 사용
class User(UserMixin):
    
    # User 객체에 저장할 사용자 정보
    # 그 외의 정보가 필요할 경우 추가한다. (ex. email 등)
    def __init__(self, user_id):
        self.user_id = user_id
    
    def get_id(self):
        return str(self.user_id)
    
    # User객체를 생성하지 않아도 사용할 수 있도록 staticmethod로 설정
    # 사용자가 작성한 계정 정보가 맞는지 확인하거나
    # flask_login의 user_loader에서 사용자 정보를 조회할 때 사용한다.
    @staticmethod
    def get_user_info(user_id, user_pw=None):
        result = dict()

        try:
            sql = ""
            sql += f"SELECT USER_ID, USER_NAME, `PASSWORD`, COMPANY_CODE, DEPARTMENT_CODE, POSITION_CODE, AUTH_CODE, "
            sql += f"REGISTER_DATETIME, LOGIN_DATETIME, LASTWEEK_REPORT_ID, THISWEEK_REPORT_ID, "
            sql += f"INSERT_USER_ID, INSERT_DATETIME, UPDATE_USER_ID, UPDATE_DATETIME "
            sql += f"FROM tn_user_info "
            if user_pw:
                sql += f"WHERE USER_ID = '{user_id}' AND `PASSWORD` = '{user_pw}'; "
            else:
                sql += f"WHERE USER_ID = '{user_id}'; "

            result = select(sql)
            
        except ex:
            result['result'] = 'fail'
            result['data'] = ex
        finally:
            return result

📑 LoginManager()

flask_login 라이브러리의 로그인 관련 기능을 담고 있는 로그인 객체이다. Flask 객체로 생성한 어플리케이션(ex.app) 을 연결한다.

app/init.py

from flask import Flask
from flask_login import LoginManager

app = Flask(__name__)  # flask 객체 생성

login_manager = LoginManager()
login_manager.init_app(app)  # app 에 login_manager 연결

요청 전 로그인된 사용자인지 판단하는 기능을 실행하기 전 사용자 정보를 조회해오는 user_loader 와 로그인된 사용자가 아닐 경우의 처리를 관리하는 unauthorized_handler 를 주로 사용한다.

app/init.py

# flask_login에서 제공하는 login_required를 실행하기 전 사용자 정보를 조회한다.
@login_manager.user_loader
def user_loader(user_id):
    # 사용자 정보 조회
    user_info = User.get_user_info(user_id)
    # user_loader함수 반환값은 사용자 '객체'여야 한다.
    # 결과값이 dict이므로 객체를 새로 생성한다.
    login_info = User(user_id=user_info['data'][0]['USER_ID'])

    return login_info
    
# login_required로 요청된 기능에서 현재 사용자가 로그인되어 있지 않은 경우
# unauthorized 함수를 실행한다.
@login_manager.unauthorized_handler
def unauthorized():
    # 로그인되어 있지 않은 사용자일 경우 첫화면으로 이동
    return redirect("/")

📑 사용자 정보 세션 저장 및 삭제

login_manager의 기능은 말그대로 도와주는 역할이다. 실제로 세션에 저장 및 삭제의 기능을 하지는 않는다. 세션에 정보를 관리하기 위해 flask_login의 login_userlogout_user 함수를 사용한다. login_user는 사용자 정보를 session에 저장하고 logout_user는 session 정보를 삭제한다.

app/view/default/common/login.py

from flask import render_template, redirect, request, url_for
from app import app
from flask_login import login_user, logout_user

from app.model.common.model_menu import Menu
from app.model.common.model_user import User


@app.route('/')
def root():
    return redirect('/login')


@app.route('/login')
def login():
    return render_template('common/template_login.html')


# 로그인 실행
# 로그인 계정 정보는 post로 받아오지만
# 일반 리소스들은 get으로 받아오므로 get과 post모두 선언해줘야 한다.
@app.route('/login/get_info', methods=['GET', 'POST'])
def login_get_info():
    user_id = request.form.get('userID')
    user_pw = request.form.get('userPW')

    if user_id is None or user_pw is None:
        return redirect('/relogin')

    # 사용자가 입력한 정보가 회원가입된 사용자인지 확인
    user_info = User.get_user_info(user_id, user_pw)

    if user_info['result'] != 'fail' and user_info['count'] != 0:
        # 사용자 객체 생성
        login_info = User(user_id=user_info['data'][0]['USER_ID'])
        # 사용자 객체를 session에 저장
        login_user(login_info)
        return redirect('/main')
    else:
        return redirect('/relogin')


# 로그인 실패 시 재로그인
@app.route('/relogin')
def relogin():
    login_result_text = "로그인에 실패했습니다. 다시 시도해주세요."
    
    return render_template('common/template_login.html', login_result_text=login_result_text)


# 로그아웃
@app.route('/logout')
def logout():
    # session 정보를 삭제한다.
    logout_user()
    return redirect('/')

📑 로그인한 사용자인지 확인 (login_required)

flask_login에서 제공하는 login_required를 이용하여 특정 요청을 실행하기 전 로그인이 필요한 기능에서 요청을 한 사용자가 로그인된 사용자인지 확인한다. 해당 사용자 정보는 user_loader를 이용한다. 로그인된 사용자가 아닐 경우 unauthorized가 실행된다.

app/view/default/common/main.py

from flask import render_template, redirect
from app import app
from flask_login import login_required

from app.model.common.model_menu import Menu


@app.route("/main")
# 로그인이 필요한 기능에 선언
@login_required
def main():
    menu_list = Menu().get_menu_list()
    if menu_list['result'] == 'fail':
        menu_list = None
    else:
        menu_list = menu_list['data']
    
    return render_template("common/layout/layout_basic.html", 
            menu_list=menu_list)

이후에 추가되는 기능에서 login_required 데코레이터만 추가하면 로그인한 사용자인지 쉽게 판단할 수 있다.

📑 로그인한 사용자인지 확인 (is_authenticated)

login_required는 해당 경로의 코드를 실행하기 전 로그인된 사용자인지 판단하여 로그인된 사용자만 기능을 실행할 수 있도록 한다. is_authenticated는 직접 로그인된 사용자인지 판단하여 특정 로직을 구현할 수 있다.

app/view/default/common/login.py

@app.route('/')
def root():
    if current_user.is_authenticated:
        return redirect('/main')
    else:
        return redirect('/login')

기본 경로로 접속 시 로그인된 사용자는 메인화면으로 로그인하지 않은 사용자는 로그인화면으로 이동


Flask-Login 0.4.1 공식문서
Flask-Login 0.5.0 깃허브


20210918 is_authenticated 관련 추가