Old Branch

Vue.js & Flask 로그인/로그아웃 (3) - Flask Auth Token 적용하기

woolbro 2020. 8. 22. 10:44
반응형

이전 글 첨부

[Master_branch/develop_branch] - Vue.js & Flask - 개발환경구축하기

[Master_branch/develop_branch] - Vue.js & Flask - 컴포넌트와 api 연동(1) - backend

[Master_branch/develop_branch] - Vue.js & Flask - 컴포넌트와 api 연동(2) - frontend

[Master_branch/develop_branch] - Vue.js & Flask - 데이터 주고받기 (axios)

[Master_branch/develop_branch] - Vue.js & Flask - 데이터 주고받기 axios(2) button binding

[Master_branch/develop_branch] - Vue.js & Flask 로그인/로그아웃 (1) - Docker DB 세팅하기

[Master_branch/develop_branch] - Vue.js & Flask 로그인/로그아웃 (2) - Flask Auth API 만들기

 

JWT 토큰을 사용해서, 현재 사용자의 인증을 체크 해 주자.

JSON Web Token (JWT) 은 웹표준 (RFC 7519) 으로서 두 개체에서 JSON 객체를 사용하여 가볍고 자가수용적인 (self-contained) 방식으로 정보를 안전성 있게 전달해줍니다.

front-back의 소스는 여기 올려놓았다

JWT Auth Token

Flask서버에서 Auth Token 발행하기

structure

backend
├── app.py
├── back.dev.Dockerfile
├── my_model
│   ├── __init__.py
│   ├── tweet_model.py
│   └── user_model.py
├── my_provider
│   ├── __init__.py
│   ├── baseball_scrapper.py
│   └── movie_scrapper.py
├── my_router
│   ├── __init__.py
│   └── api
│       ├── __init__.py
│       ├── auth.py
│       └── tweet.py
├── my_util
│   ├── __init__.py
│   ├── auth_util.py
│   └── my_logger.py
└── requirements.txt

구조를 보면 알겠지만, backend의 구조가 많이 변경되었다.

기존의 app.py에서 작성 되어있던 라우터들을 jwt토큰을 사용 한 버전 2의 api를 사용하기 위해서 my_router로 빼내었다.

이번에는 'tweet'라는 포스트를 추가하는 동작을 해보려고 한다.

먼저 필요한 모듈을 다운받고, 데이터 저장에 필요한 db를 생성 해 주도록 하겠다.

Install pyjwt

(venv)$ pip3 install PyJWT

create table

create table tweet(
    id int(5) not null auto_increment,
    title varchar(100) not null,
    words text not null,
    creator varchar(20) not null,
    created_at datetime default current_timestamp,
    updated_at datetime default current_timestamp on update current_timestamp,
    PRIMARY KEY (id,creator) 
)

create flask model(backend/my_model/tweet_model.py)

# coding: utf-8
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text
from sqlalchemy.schema import FetchedValue
from sqlalchemy.orm import relationship
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class Tweet(db.Model):
    __tablename__ = 'tweet'

    id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
    title = db.Column(db.String(100), nullable=False)
    words = db.Column(db.Text, nullable=False)
    creator = db.Column(db.String(100), nullable=False, primary_key=True)
    created_at = db.Column(db.DateTime, server_default=db.FetchedValue())
    updated_at = db.Column(db.DateTime, server_default=db.FetchedValue())

    def to_dict(self):
        return dict(id=self.id,
                    title=self.title,
                    words=self.words,
                    creator=self.creator,
                    created_at=self.created_at,
                    updated_at=self.updated_at)

이제 데이터를 생성/조회 할 수 있도록 세팅이 되었다.

이제 권한과 관련된 작업을 해 주도록 하겠다.

backend/my_util/auth_util.py

import jwt
from flask import request, jsonify, current_app
from my_model.user_model import User
from my_util.my_logger import my_logger
from functools import wraps

def token_required(f):
    @wraps(f)
    def _verify(*args, **kwargs):
        auth_headers = request.headers.get("Authorization", '').split()

        invalid_msg = {
            'message': "Invalid Token. Registeration / authentication required",
            'authenticated': False
        }

        expired_msg = {
            'message': "expired Token. Reauthentication required",
            'authenticated': False
        }

        if len(auth_headers) != 2:
            return jsonify(invalid_msg), 401

        try:
            token = auth_headers[1]
            data = jwt.decode(token, 'qwersdaiofjhoqwihlzxcjvjl')
            parse_name = data['sub']
            user = User.query.filter_by(username=parse_name).first()
            if not user:
                raise RuntimeError('User not found')
            return f(user, *args, **kwargs)
        except jwt.ExpiredSignatureError:
            return jsonify(expired_msg), 401  # 401 is Unauthorized HTTP status code
        except (jwt.InvalidTokenError, Exception) as e:
            my_logger.error(e)
            return jsonify(invalid_msg), 401

    return _verify

토큰을 생성하고, 확인하는 작업까지 완료되었다.

이제 어떤 라우터에서든, 위의 모듈을 import 하고 데코레이터 형태로 사용하면 인증이 필요한 절차가 된다.

backend/my_router/api/auth.py

from flask import Blueprint, request, jsonify, current_app
from my_util.my_logger import my_logger
from my_model import user_model

import jwt
import datetime

auth_route = Blueprint('auth_route', __name__)

@auth_route.route('/logout', methods=['POST'])
def logout():
    return jsonify("hi!")

@auth_route.route('/get_user', methods=['GET'])
def get_login_user():
    return jsonify("hi!")

@auth_route.route('/signup', methods=['POST'])
def user_register():
    # 로그
    my_logger.info("user register")
    data = request.get_json()
    db = user_model.db

    user_data = user_model.User.query.filter_by(username=data.get('username')).first()
    if user_data is not None:
        my_logger.error("Username is Already exist")
        return {"success": "username is already exist"}

    user = user_model.User(**data)
    user.has_password()
    db.session.add(user)
    db.session.commit()

    my_logger.info("user Save Success")

    return jsonify(user.to_dict()), 200

@auth_route.route('/login', methods=['POST'])
def login():
    my_logger.info("User Login")

    data = request.get_json()
    user_data = user_model.User.query.filter_by(username=data.get('username')).first()

    if user_data is not None:
        auth = user_data.check_password(data.get("userpwd"))

        if not auth:
            my_logger.error("Authentication valid error")
            return jsonify({'message': 'Authentication valid error', "authenticated": False}), 401

        my_logger.info("User Authentication token setting")
        token = jwt.encode({
            'sub': user_data.username,
            'iat': datetime.datetime.utcnow(),
            'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
        }, 'qwersdaiofjhoqwihlzxcjvjl')

        my_logger.info("Set up Success")
        my_logger.debug(token.decode('UTF-8'))
        return jsonify({"token": token.decode('UTF-8')})
    else:
        my_logger.error("User Does Not Exist")
        return jsonify({'message': 'User Does Not Exist', "authenticated": False}), 401

backend/my_router/api/tweet.py

from flask import Blueprint, request, jsonify, current_app
from my_util.my_logger import my_logger
from my_model import user_model
from my_model import tweet_model
from my_util.auth_util import token_required

import jwt
import datetime

tweet_route = Blueprint('tweet_route', __name__)

@tweet_route.route('/tweet', methods=["GET"])
@token_required
def tweet_list(current_user):
    my_logger.info("get Tweet List")
    tweet_list = tweet_model.Tweet.query.all()
    return jsonify([t.to_dict() for t in tweet_list])

@tweet_route.route('/tweet', methods=["POST"])
@token_required
def create_tweet(current_user):
    my_logger.info("Tweet Post!")
    try:
        data = request.get_json()
        title = data.get("title")
        words = data.get("words")
        created_at = datetime.datetime.utcnow()
        updated_at = datetime.datetime.utcnow()

        db = tweet_model.db
        tweet = tweet_model.Tweet(
            title=title,
            words=words,
            creator=current_user.username,
            created_at=created_at,
            updated_at=updated_at
        )
        db.session.add(tweet)
        db.session.commit()
        return jsonify(tweet.to_dict()), 200
    except Exception as e:
        my_logger.error(e)
        return jsonify({'message': 'fail to save tweet'}), 200

backend는 완성했으니, 이제 Vue를 작업 해 보도록 하자...! 너무 어렵다..