API 개발/Restful API

Restful API : 회원가입/로그인 API 에서, 토큰(JWT) 생성해서 처리하는 방법

신강희 2024. 5. 28. 17:18
728x90

< 회원가입/로그인 API 에서, 토큰(JWT) 생성해서 처리하는 방법 >

 

# 이전 장에서 환경세팅은 모두 완료하였고, 이제 진짜 회원관리 API 개발을 해보자

- postman에서 우선 API 설계 및 리퀘스트 생성

- POST로 선택하고, 경로와 Body문 작성

 

# 이제 Visual Studio Code를 실행

- 우선 install이 되어있지 않다면 cmd 창에서 install 먼저 진행

pip install flask-jwt-extended

 

# config.py 파일과 app.py 환경 세팅 문구는 이전 게시글과 동일하게 작성해 주어야 한다.

- 참고 : https://sorktjrrb.tistory.com/149

 

Restful API : Flask 에서 JWT 사용하는 방법 (설치방법 및 flask에서 설정방법)

(설치방법 및 flask에서 설정방법) > # JWT (JSON Web Token) - 웹 애플리케이션에서 사용자 인증 및 권한 부여를 위한 표준화된 방법 중 하나 - JWT는 사용자 인증에 사용되며 사용자와 서버 사이에서 전

sorktjrrb.tistory.com

 

# 회원 가입 API 작성

- 작업 용이성을 위해서 resources라는 폴더를 생성하여서 user.py라는 파일 생성후 작성

 
import datetime
from email_validator import EmailNotValidError, validate_email

from flask import request
from flask_jwt_extended import create_access_token, jwt_required, get_jwt
from mysql.connector import Error
from flask_restful import Resource

from mysql_connection import get_connection
from utils import check_password, hash_password

# 회원가입 API
class UserRegisterResource(Resource) :
    def post(self) :
        # 1. 클라이언트가 보낸 데이터를 받아준다.
        # 포스트맨으로 작성한 body 데이터를 json으로 받아온다.
        data = request.get_json()
        print(data)
        # 2. 데이터가 모두 있는지 확인 (두가지 방법)
        # 1) if 문으로 나누어서 작성 (이게 좀더 직관적으로 구분되므로 해당 방법 추천)
        #if 'email' not in data or 'username' not in data or 'password' not in data :
        #    return {"result" : "fail"}, 400
       
        #if data['email'].strip() == '' or data['username'].strip() == '' or data['password'].strip() == '' :
        #    return {"result" : "fail"}, 400
        # 2) if 문 길게 한줄로 작성
        if data.get('email') is None or data.get('email').strip() == '' or \
            data.get('username') is None or data.get('username').strip() == '' or \
            data.get('password') is None or data.get('password').strip() == '':
            return {'result' : 'fail'}, 400

        # 3. 이메일주소 형식이 올바른지 확인
        # 지정된 형식인 EmailNotValidError 사용 (정의된 라이브러리이므로 해당 형식 그대로 사용해야함)
        try :
            validate_email(data['email'])
        except EmailNotValidError as e :
            return {'result' : 'fail', 'error' : str(e)}, 400

        # 4. 비밀번호 길이가 유효한지 체크한다.
        #    예) 비번은 4자리 이상 12 자리 이하
        if len(data["password"]) < 4 or len(data["password"]) > 12 :
            return {'result' : 'fail'}, 400

        # 5. 비밀번호를 암호화 한다.
        password = hash_password( data['password'])
        print(password)

        # 6. DB에 저장한다.
        try :
            connection = get_connection()
            # 이번 쿼리문 작성 주의할점! record에 password는 암호화된 패스워드를 가져와야 하기 때문에 상단에서 암호화한 변수로 지정해줘야함!
            query = '''insert into user
                (username, email, password)
                values
                (%s, %s, %s);'''
            record = (data['username'], data['email'], password)
            cursor = connection.cursor()
            cursor.execute(query, record)
            connection.commit()

            ### 중요하다!!!
            ### DB에 회원가입하여, user 테이블에 insert 된 후,
            ### 이 user 테이블의 id 값을 가져와야 한다.
            ### 생성한 변수를 마지막 리턴에 작성하여 클라이언트에게 보내준다.
            user_id = cursor.lastrowid

            cursor.close()
            connection.close()

        except Error as e:
            if cursor is not None:
                cursor.close()
            if connection is not None:
                connection.close()
            return {'result' : 'fail'}, 500
       
        # 6-2. user_id를 바로 클라이언트에게 보내면 안되고,
        ##     JWT 로 암호화 해서, 인증토큰을 보내야 한다.
        ### 토큰 만료시킬때 사용
        # access_token = create_access_token(user_id,
        #                                    expires_delta= datetime.timedelta(minutes=3))
        access_token = create_access_token(user_id, )
           
        # 7. 응답할 데이터를 JSON으로 만들어서 리턴!

        return {"result" : "success", 'access_token' : access_token}
   
        # 이렇게 생성한 user_id는 결국 생성한 레시피를 DB에 저장할때 구분하기 위해서 만든것이므로 생성한 user_id 컬럼을 레시피 파일에 메뉴 생성 API 함수에다가 추가해줘야한다.
        # 이제 수정이든, 삭제든 user_id가 필요해졌는데 이게 보안되지 않으면 제3의 인물이 user_id를 동일하게 해서 생성하거나 삭제할수도 있다. 이게 해킹
        # 그러므로 인증토큰(jwt)이 필요하다! jwt 라이브러리 설치! pip install flask-jwt-extended
        # 하고 결과에 access_token으로 생성한 변수를 추가해 준다.
 

 

# app.py에도 경로 연동

 
# API를 처리하는 코드는
# Resource 클래스를 상속받아서 작성한다.
# 이 클래스에는 get, post, put, delete 함수를 상속받는다.
# 따라서 이 함수들을, 우리의 서비스에 맞게 수정해서 사용하면된다.

from flask import Flask
from flask_restful import Api
# 생성한 API class 사용을위해서 생성시마다 import 필요!
from resources.recipe import RecipeListResource, RecipePublishResource, RecipeResource
from resources.user import UserLoginResource, UserLogoutResource, UserRegisterResource

from flask_jwt_extended import JWTManager
from config import Config

from resources.user import jwt_blacklist

app = Flask(__name__)

# 환경변수 셋팅 (jwt 사용을 위한)
app.config.from_object(Config)

# JWT 매니저 초기화
jwt = JWTManager(app)

# 로그아웃된 토큰으로 요청하는 경우, 처리하는 함수 작성
@jwt.token_in_blocklist_loader
def check_if_token_is_revoked(jwt_header, jwt_payload) :
    jti = jwt_payload['jti']
    return jti in jwt_blacklist

api = Api(app)

# 경로(path)와 리소스(API 코드)를 연결한다.
# <int> flask 문법임 외워야됨
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')
api.add_resource( UserLogoutResource, '/users/logout')

if __name__ == '__main__' :
    app.run()
 

 

# 이제 포스트맨을 실행시켜서 SEND 동작을 해보자

- 문제가 없다면 success 문구와 암호화된 인증 토큰이 생성됨

 

# 바로 이어서 로그인 API 개발해 보자

- postman을 실행해서 리퀘스트를 생성 (이때, 당연히 회원가입한 이메일과 비밀번호를 입력해야 한다!)

- 동일한 user.py 파일에 이어서 코드 작성

 
# 로그인 API
class UserLoginResource(Resource) :
    def post(self) :

        # 1. 클라이언트로부터 데이터를 받는다.
        data = request.get_json()

        if 'email' not in data or 'password' not in data:
            return {'result' : 'fail'}, 400
        if data['email'].strip() == '' or data['password'].strip() == '':
            return {'result' : 'fail'}, 400

        # 2. DB로부터 이메일에 해당하는 유저 정보를 가져온다.
        try :
            connection = get_connection()
            query = '''select *
                from user
                where email = %s;'''
            record = (data['email'] , )
            cursor = connection.cursor(dictionary=True)
            cursor.execute(query, record)

            result_list = cursor.fetchall()

            print(result_list)

            cursor.close()
            connection.close()

        except Error as e:
            if cursor is not None:
                cursor.close()
            if connection is not None:
                connection.close()
            return {'result':'fail', 'error':str(e)},500

        # 3. 회원인지 확인한다.
        if result_list == [] :
            return {'result' : 'fail'} , 401

        # 4. 비밀번호를 체크한다.
        # 유저가 입력한 비번 data['password']
        # DB에 암호화된 비번 result_list[0]['password']
        isCorrect = check_password(data['password'] , result_list[0]['password'])
        if isCorrect == False :
            return {'result' : 'fail'} , 401

        # 5. 유저아이디를 가져온다.
        user_id = result_list[0]['id']

        # 6. JWT 토큰을 만든다.
        # access_token = create_access_token(user_id,
        #                                    expires_delta= datetime.timedelta(minutes=3))
        access_token = create_access_token(user_id,)

        # 7. 클라이언트에 응답한다.

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

 

# 저장후 서버를 한번 종료시켰다가 다시 flask run

- postman에서 테스트해 보면 회원가입 API와 동일하게 success 문구와 인증토큰이 생성된다.

 

다음 게시글로 계속~!

 

반응형