RESTful flask API - 회원을 DB에 등록하고 로그인을 할 수 있는 기능 구현하기

반응형

이번 포스팅은 DB의 user table을 이용하여 로그인을 위한 회원 등록을 하고, 로그인하는 기능을 서술합니다.

보안을 위해 유저의 식별 가능한 id는 토큰화하며 입력한 비밀번호는 암호화하여 DB에 저장합니다.

DB 연동 과정은 이전 포스팅을 참고해주세요.

https://luvris2.tistory.com/182

 

API서버 - DB 연동하기

API서버 DB연동하기 DB : MySQL Programming language : Python IDE : Visual Studio Code MySQL DB 생성 (recipe) Table 생성 (recipe, user) DB를 관리 할 수 있는 권한 설정하기 (MySQL) SQL 쿼리 use mysql;..

luvris2.tistory.com


필요 라이브러리 설치

  • passlib : 암호화를 위한 라이브러리
  • psycopg2-binary : 데이터베이스의 값 추가, 수정을 편리하게 사용하기 위한 라이브러리
  • flask-jwt-extended : 데이터를 토큰화하여 암호화
pip install passlib
pip install psycopg2-binary
pip install flask-jwt-extended

Visual Studio Code

API 서버 구축

  • 메인 파일 : app.py
  • 회원가입을 통한 회원 등록
    • 서버 URL : 로컬호스트/users/register
  • 로그인
    • 서버 URL : 로컬호스트/users/login
# app.py
from flask import Flask
from flask_restful import Api
from user import UserRegisterResource, UserLoginResource

app = Flask(__name__)

api = Api(app)

# 경로와 리소스(api코드) 연결
api.add_resource(UserRegisterResource, '/users/register') # 회원가입
api.add_resource(UserLoginResource, '/users/login') # 로그인

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

기능 설계 - 비밀번호와 이메일의 유효성 검사

  • 사용자 지정 함수 파일 : utils.py
  • restful api의 리소스화는 따로 하지 않고 사용자 지정 함수 파일로 저장
  • 필요한 때에 파일 임포트하여 함수 호출
  • email_validator 라이브러리를 이용하여 이메일 유효성 검사
  • passlib 라이브러리를 이용하여 비밀번호 암호화 (해시)
from passlib.hash import pbkdf2_sha256 # 암호화, 일반적으로 사용 sha256

# 원문 비밀번호를 암호화 하는 함수
# hash : 암호화 하는 함수
def hash_password(original_password) :
    salt = 'yh*hello12'
    password = original_password + salt
    password = pbkdf2_sha256.hash(password)
    return password

# 비밀번호가 맞는지 확인하는 함수
# verify : 두 데이터가 동일하는지 확인하는 함수
def check_password(original_password, hashed_password) :
    salt = 'yh*hello12'
    check = pbkdf2_sha256.verify(original_password+salt, hashed_password)
    return check # Return value : True/False

기능 설계 - 회원의 식별 ID를 토큰화

  • 사용자 지정 함수 파일 : config.py
    • 토큰화를 위한 클래스 Config 정의
    • JWT_SECRET_KEY : 임의의 값을 입력, 암호화 키이기 때문에 절대 노출되면 안됨, 테스트용
    • JMT_ACCESS_TOKEN_EXPIRES : 토큰의 유효 기간, 테스트용이기 때문에 False 설정
    • PROPAGATE_EXCEPTIONS : 예외처리를 JWT로 처리, True로 설정
class Config :
    # 암호화 키
    JWT_SECRET_KEY = 'eunbyeol0413##hello' # 주의 : 노출되면 절대 안됨
    JWT_ACCESS_TOKEN_EXPIRES = True # 토큰 유지 시간
    PROPAGATE_EXCEPTIONS = True # 예외처리를 JWT로 처리

기능 설계 - 회원가입 절차를 통한 회원 정보 DB에 등록

  • 추가 리소스 파일 : user.py
    • 클래스 UserRegisterResource를 자원화
    • POST를 사용하여 회원의 정보를 DB로 전달
    • 비밀번호 암호화와 이메일 유효성 검사를 위한 사용자 지정 함수 파일 호출 (utils.py)
      • from utils import check_password, hash_password
    • if문을 통한 비밀번호 4-12자리수 유효성 검사
    • 클라이언트에서 입력한 회원 정보를 user테이블에 저장
    • 커서의 lastrowid 값을 이용하여 유저의 식별을 위한 식별 정보(id)를 user_id에 저장하여 출력
    • 식별 정보(id)는 해킹의 위험이 있으므로 보안을 위해 토큰화
from flask import request
from flask_restful import Resource
from flask_jwt_extended import create_access_token

import mysql.connector
from mysql_connection import get_connection

from email_validator import validate_email, EmailNotValidError
from utils import check_password, hash_password

class UserRegisterResource(Resource) :
    def post(self) :
        # 데이터 교환 형식
        # {
        #     "user_name": "홍길동",
        #     "email": "abc@naver.com",
        #     "password": "1234"
        # }
        data = request.get_json()

        # 이메일 주소 형식 확인, email_validator 사용
        try :
            validate_email( data['email'] )
        except EmailNotValidError as e:
        # email is not valid, exception message is human-readable
            print(str(e))
            return {'error' : str(e) }, 400
        
        # 비밀번호의 길이 유효 체크, 4~12자리
        if len(data['password']) < 4 or len(data['password']) > 12 :
            return { "error" : "비밀번호의 길이를 확인해주세요 (4-12자리)" }, 400

        # 비밀번호 암호화, passlib 사용
        # data['password']
        hashed_password = hash_password( data['password'] )
        
        query = '''
        insert into user
            (username, email, password)
        values
            (%s ,%s ,%s);
        '''
        record = ( data['username'], data['email'], hashed_password )
        connection = get_connection()
        cursor = connection.cursor()
        cursor.execute(query, record)
        connection.commit()

        # DB에 저장된 ID 컬럼의 값 가져오기
        user_id = cursor.lastrowid

        cursor.close()
        connection.close()
        
        # 'user_id' JWT 암호화
        access_token = create_access_token(user_id)
        
        except mysql.connector.Error as e :
            print(e)
            cursor.close()
            connection.close()
            return {"error" : str(e)}, 503 #HTTPStatus.SERVICE_UNAVAILABLE

        return {
            "result" : "success",
            "access_token" : access_token,
            "hash password" : hashed_password
         }

POSTMAN

API 기능 테스트 - 회원가입, 회원의 정보가 올바르면 DB에 저장

  • POST- 로컬호스트/users/register
  • 회원 정보 입력 후 Send

 

  • 이메일과 비밀번호를 올바르게 입력하면  'success' 출력
    • 회원의 식별 아이디 토큰화, 비밀번호 암호화하여 저장

 

 

  • 이메일 형식이 올바르지 않으면 에러 출력

 

  • 비밀번호 자리 유효성 확인 (4-12자리) : 3자리 입력, 에러 출력


MySQL

  • 정상적으로 입력할 경우 회원 정보 저장
  • 비밀번호는 해시를 이용하여 암호화


Visual Studio Code

기능 설계 - 이메일과 비밀번호를 입력받아 로그인

  • 추가 리소스 파일 : user.py
  • 클래스 UserLoginResource를 자원화
  • 회원 등록 기능과 같은 파일에 위치하므로 위의 소스코드 밑에 붙여넣기
  • POST를 사용하여 입력 받은 값으로 회원 식별
    • 이메일의 존재 유무를 확인
    • 해시화된 비밀번호와 입력한 비밀번호의 동일성을 확인 (passlib 라이브러리의 verify함수)
    • 식별 확인시 식별 정보(id)를 토큰화
class UserLoginResource(Resource) :
    def post(self) :
        data = request.get_json()
        connection = get_connection()

        try :
            query = '''
                        select * from user
                        where email = %s;
                    '''
            record = ( data['email'], )
            cursor = connection.cursor(dictionary=True)
            cursor.execute(query, record)
            result_list = cursor.fetchall()
            i = 0
            for record in result_list :
                result_list[i]['created_at'] = record['created_at'].isoformat()
                result_list[i]['updated_at'] = record['updated_at'].isoformat()
                i += 1
            cursor.close()
            connection.close()

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

        # result_list = 1 : 유저 데이터 존재, 0 : 데이터 없음
        if len(result_list) != 1 :
            return {"error" : "존재하지 않는 회원입니다."}, 400

        # 비밀번호 확인
        user_info = result_list[0]
        check = check_password( data['password'], user_info['password'] )
        if check == False :
            return {"error" : "비밀번호가 맞지 않습니다."}, 400

        # 'user_id' JWT 암호화
        access_token = create_access_token(user_info['id'])

        return { "result" : "success", "access_tokken" : access_token }, 200

POSTMAN

API 기능 테스트

  • POST- 로컬호스트/users/login
  • 로그인 할 이메일과 비밀번호 입력 후 Send
  • 등록된 회원 정보 확인
    • password는 암호화 (비밀번호:1234)

 

 

  • 등록되지 않은 이메일 입력의 경우 에러 출력

 

  • 비밀번호가 올바르지 않을 경우 에러 출력

 

  • 이메일과 비밀번호 모두 정상적으로 입력한 경우 식별 아이디 출력
    • 보안상 식별 아이디(id)는 토큰화하여 출력

반응형