[컴][웹] tko22/flask-boilerplate 에 login 부분 추가

 login / restful api / login 부분 구현 / 추가

tko22/flask-boilerplate 에 login 부분 추가

tko22/flask-boilerplate(tko22) 는 Rest Api server 에 맞는 구조이다. 그래서(?) login 부분이 들어가 있지 않다. 그래서 hack4impact/flask-base(hack4impact) 에 구현되어 있는 login 부분을 tko22/flask-boilerplate 의 구조에 맞춰서 변경했다.

절차

  1. pip install Flask_Login
  2. pip install webargs
  3. api/models/user.py 추가
    • api/models/user.py 에 user table 의 model 생성
    • api/models/__init__.pyfrom .user import * 추가
  4. Flask-Login setup
    • flask 에 LoginManager 추가: api/__init__.py 에서 LoginManager() 추가
  5. api/views/main.py 에 login api 추가

hack4impact에서는 Flask_Login을 사용하기 때문에 먼저 Flask_Login 을 설치한다. 그리고 parameter validate 를 하기 위해서 webargs package 를 설치한다.

user.py

로그인과 관련한 user 의 정보를 가진 user table 에 대한 model 을 생성한다. 그리고 이것에 대한 import 를 편하게 하기 위해서 init.py 에 import 를 해놓자.

더불어 flask-login 에서 필요로 하는 몇가지를 추가해야 한다. 그렇지 않으면 api request 가 제대로 동작하지 않는다.

# /api/models/user.py

from api.core import Mixin
from .base import db
from flask_login import UserMixin


class UserAccounts(UserMixin, db.Model):
    __tablename__ = "user_accounts"

    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String)
    encrypted_password = db.Column(db.String)
    created_at = db.Column(db.DateTime)
    updated_at = db.Column(db.DateTime)

    def __init__(self, **kwargs):
        super(UserAccounts, self).__init__(**kwargs)
        # if self.role is None:
        #     if self.email == current_app.config["ADMIN_EMAIL"]:
        #         self.role = Role.query.filter_by(
        #             permissions=Permission.ADMINISTER
        #         ).first()
        #     if self.role is None:
        #         self.role = Role.query.filter_by(default=True).first()

    def verify_password(self, password):
        # return check_password_hash(self.password_hash, password)
        return self.encrypted_password == password

    def __repr__(self):
        return "<User '%s'>" % self.full_name()


# flask-login setup
class AnonymousUser(AnonymousUserMixin):
    def can(self, _):
        return False

    def is_admin(self):
        return False

login_manager.anonymous_user = AnonymousUser


@login_manager.user_loader
def load_user(user_id):
    return UserhabitAccounts.query.get(int(user_id))
# /api/models/__init__.py
from .user import *

Flask-Login setup

api/__init__.py 에서 flask app 을 만들게 되는데, 이때 LoginManager를 추가해 줘야 한다.

# Set up Flask-Login
login_manager = LoginManager()
login_manager.session_protection = "strong"
login_manager.login_view = "account.login"
...
def create_app(test_config=None):
    ...
    login_manager.init_app(app)
    ...

login api

이제 실체 login 부분을 만들어 보자. api/views/main.py 에 api 를 추가한다.

...
from flask_login import login_user
from api.models import UserAccounts
from webargs.flaskparser import parser

# visit /login
# for validator,
# https://marshmallow.readthedocs.io/en/latest/marshmallow.validate.html#module-marshmallow.validate
@main.route("/login", methods=["POST"])
@parser.use_args(
    {
        "email": fields.String(required=True, validate=validate.Email()),
        "password": fields.String(required=True),
    },
    location="json",
)
def post_login(args):

    user = (
        UserAccounts.query
        .filter(UserAccounts.email == args["email"])
        .first()
    )
    if (
        user is not None
        and user.encrypted_password is not None
        and user.verify_password(args["password"])
    ):
        login_user(user)
        return create_response(
            message="You are now logged in. Welcome back!"
        )

    return create_response(
        status=422, message="아이디 패스워드를 확인해주세요."
    )

test

아래처럼 curl 을 날리면 된다. 아래는 windows curl 이다.

curl -X POST -H "Content-Type: application/json" -d "{\"email\":\"test@gmail.com\",\"password\":\"ddkjfkdlsjltehthp\"}" http://127.0.0.1:5000/login

webargs 이용시, error response 를 json 형식으로

400, 422 error 가 발생할 때 json 형식으로 response 를 주게 된다.

from flask import jsonify


# Return validation errors as JSON
@app.errorhandler(422)
@app.errorhandler(400)
def handle_error(err):
    headers = err.data.get("headers", None)
    messages = err.data.get("messages", ["Invalid request."])
    if headers:
        return jsonify({"errors": messages}), err.code, headers
    else:
        return jsonify({"errors": messages}), err.code

댓글 없음:

댓글 쓰기