개발공부

[Flask 에서 JWT 사용] 로그인한 유저만 처리할 수 있는 API에 토큰 적용하는 방법 본문

Python/Flask

[Flask 에서 JWT 사용] 로그인한 유저만 처리할 수 있는 API에 토큰 적용하는 방법

mscha 2022. 6. 21. 09:33

JWT를 이용해 로그인한 유저만 처리할 수 있도록 하려면, 해당 기능의 함수 위에

@jwt_required()를 추가로 입력해야 한다. 그러면 클라이언트의 header 로부터 토큰을 받아오는데 

이는 get_jwt_identity() 함수로 복호화하여 받아올 수 있다.

 

예제 

mysql_connection.py

import mysql.connector

def get_connection() :
    connection = mysql.connector.connect(
        host = 'yh-db.cesawiuivilv.ap-northeast-2.rds.amazonaws.com',
        database = 'recipe_db',
        user = 'recipe_user',
        password = 'recipe1234',
    )
    return connection

utils.py

from passlib.hash import pbkdf2_sha256

# 원문 비밀번호를, 암호화 하는 함수
# 암호화는 할 수 있지만 복호화는 안되는 hash 사용
# 단방향 암호화
# hash의 패턴을 파악하지 못하도록 salt(seed)를 설정하여 사용한다.
def hash_password(original_password) :
    salt = 'yh*hello12'
    password = original_password + salt
    password = pbkdf2_sha256.hash(password)
    return password

# 비밀번호가 맞는지 확인하는 함수
# True, False로 리턴한다.
def check_password(original_password, hashed_password) :
    salt = 'yh*hello12'
    check = pbkdf2_sha256.verify(original_password+salt, hashed_password)
    return check

conpig.py

class Config :
    JWT_SECRET_KEY = 'mykey'
    JWT_ACCESS_TOKEN_EXPIRES = False
    PROPAGATE_EXCEPTIONS = True

app.py

from flask import Flask
from flask_jwt_extended import JWTManager
from flask_restful import Api
from config import Config

from resources.recipe import RecipeListResource
from resources.recipe_info import RecipeResource
from resources.recipe_publish import RecipePublishResource
from resources.user import UserLoginResource, UserRegisterResource

app = Flask(__name__)

# 환경변수 셋팅
app.config.from_object(Config)

# JWT 토큰 라이브러리만들기
jwt = JWTManager(app)

api = Api(app)

# 경로와 리소스(API 코드)를 연결한다.
# <int:recipe_id>
# 클라이언트에서 정수 recipe_id를 받아서 변수로 저장한다.
api.add_resource(RecipeListResource, '/recipes')
api.add_resource(RecipeResource, '/recipes/<int:recipe_id>')
api.add_resource(RecipePublishResource, '/recipes/<int:recipe_id>/publish')
api.add_resource(UserRegisterResource, '/users/register')
api.add_resource(UserLoginResource, '/users/login')
if __name__ == '__main__' :
    app.run()

 

/resource/recipe_info.py

class RecipeResource(Resource) :
    # 데이터를 업데이트하는 API들은 put 함수를 사용한다.
    @jwt_required()
    def put(self, recipe_id) :
        # body에서 전달된 데이터를 처리
        data = request.get_json()

        user_id = get_jwt_identity()

        # DB 업데이트 실행코드
        try :
            # 데이터 Update
            # 1. DB에 연결
            connection = get_connection()
            
            ### 먼저 recipe_id 에 들어있는 user_id가
            ### 이 사람인지 먼저 확인한다.
            query = '''select user_id from recipe
                        where id = %s;'''
            record = (recipe_id, )

            cursor = connection.cursor(dictionary = True)
            cursor.execute(query, record)

            result_list = cursor.fetchall()

            if len(result_list) == 0 :
                cursor.close()
                connection.close()
                return {'error' : '레시피 아이디가 잘못되었습니다.'}, 400
                
            recipe = result_list[0]

            if recipe['user_id'] != user_id :
                cursor.close()
                connection.close()
                return {'error' : '다른 사용자의 레시피를 수정할 수 없습니다.'}, 401

            
            # 2. 쿼리문 만들기
            query = '''Update recipe
                    set name = %s, description = %s, cook_time = %s, directions = %s
                    where id = %s;'''                 

            record = (data['name'], data['description'], 
                        data['cook_time'], data['directions'], recipe_id)

            # 3. 커서를 가져온다.
            cursor = connection.cursor()

            # 4. 쿼리문을 커서를 이용해서 실행한다.
            cursor.execute(query, record)

            # 5. 커넥션을 커밋해줘야 한다 => 디비에 영구적으로 반영하라는 뜻
            connection.commit()


            # 6. 자원 해제
            cursor.close()
            connection.close()
        except mysql.connector.Error as e :
            print(e)
            cursor.close()
            connection.close()
            return {"error" : str(e)}, 503

        return {'result' : 'success'}, 200

/resource/recipe.py

from http import HTTPStatus
from flask import request
from flask_jwt_extended import get_jwt_identity, jwt_required
from flask_restful import Resource
from mysql.connector.errors import Error
from mysql_connection import get_connection
import mysql.connector

### API 를 만들기 위한 클래스 작성
### class(클래스) 란 변수와 함수로 구성된 묶음 !
### 클래스는 상속이 가능하다.
### API를 만들기 위한 클래스는 flask_restful 라이브러리의
### Resource 클래스를 상속해서 만들어야 한다.

class RecipeListResource(Resource) :
    # restful api 의 method 에 해당하는 함수 작성
    
    @jwt_required()
    def post(self) :
        # api 실행 코드를 여기에 작성
        
        # 클라이언트에서 body 부분에 작성한 json을 
        # 받아오는 코드
        data = request.get_json()

        # header 부분에서 받아온
        # token을 복호화해서 가져오는 코드
        user_id = get_jwt_identity()

        # 받아온 데이터를 디비 저장하면 된다.
        try :
            # 데이터 insert
            # 1. DB에 연결
            connection = get_connection()
            
            # 2. 쿼리문 만들기
            query = '''insert into recipe
                    (name, description, cook_time, directions, user_id)
                    values
                    (%s, %s, %s, %s, %s);'''
                    
            # recode 는 튜플 형태로 만든다.
            recode = (data['name'], data['description'], data['cook_time'], data['directions'], user_id)

            # 3. 커서를 가져온다.
            cursor = connection.cursor()

            # 4. 쿼리문을 커서를 이용해서 실행한다.
            cursor.execute(query, recode)

            # 5. 커넥션을 커밋해줘야 한다 => 디비에 영구적으로 반영하라는 뜻
            connection.commit()

            # 6. 자원 해제
            cursor.close()
            connection.close()

        except mysql.connector.Error as e :
            print(e)
            cursor.close()
            connection.close()
            return {"error" : str(e)}, 503

        # 숫자는 HTTPStatus 번호이다.
        return {"result" : "success"}, 200

 

 

postman에서 테스트 할 때는 header 부분에 아래와 같은 식으로

key : Authorization 

value : Bearer 토큰

으로 하고 send 하면된다.