JSON Web Token 에 대한 설명
사용자 인증 (User Authentication)에 사용되며 사용자와 서버에 전달된다.
- JWT는 header, payload, signature 각각 base64 encoding 한 후 concat한 문자열이다.
- 토큰에 포함된 내용들은 암호화되지 않아서 누구나 확인 할 수 있다.
- 토큰에 포함되는 여러 종류의 필드들이 용도별로 미리 정의 되어있어서 일관성 있는 사용이 가능하다.
- signature를 이용하여 해당 토큰이 실제로 원래 발급자가 발급했던 유효한 토큰인지 검증을 할 수 있다.
- signature 생성을 위한 알고리즘을 원하는 대로 선택이 가능하다. (ex: RS256, HS256 등)
- 실제 생성된 JWT 스트링의 샘플은 https://jwt.io 에서 손쉽게 확인 가능하다.
https://www.letmecompile.com/api-auth-jwt-jwk-explained/
Flask-JWT-Extended 라이브러리 설치하기.
라이브러리 설치
$ pip install flask-jwt-extended
https://flask-jwt-extended.readthedocs.io/en/latest/options/
Configuration Options — flask-jwt-extended 4.4.1 documentation
Configuration Options You can change many options for this extension works via Flask’s Configuration Handling. For example: app.config["OPTION_NAME"] = option_value General Options: JWT_TOKEN_LOCATION Where to look for a JWT when processing a request. Th
flask-jwt-extended.readthedocs.io
config.py 파일에, JWT 암호화를 위한 변수 추가
다음을 셋팅한다.
- -SECRET_KEY : 암호화 하기 위한 키. 노출되면 안된다.
예
config.py
SECRET_KEY = 'super-secret-key'
회원가입 / 로그인 API에 토큰 생성하기
class UserRegisterResource(Resource) :
def post(self) :
# {
# "username" : "홍길동",
# "email" : "abc@naver.com",
# "password" : "1234"
# }
# 클라이언트가 body 에 보내준 json 을 받아온다.
data = request.get_json()
# 2. 이메일 주소형식이 제대로 된 주소형식인지
# 확인하는 코드 작성.
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
# 3. 비밀번호의 길이가 유효한지 체크한다.
if len(data['password']) < 4 or len(data['password']) > 12 :
return {'error' : '비밀번호 길이를 확인하시오.'}, 400
# 4. 비밀번호를 암호화 한다.
# data['password']
hashed_password = hash_password(data['password'])
# 5. 데이터베이스에 회원정보를 저장한다.
try :
# 데이터 업데이트
# 1. DB에 연결
connection = get_connection()
# 2. 쿼리문 만들기
query = '''insert into user
(username, email, password)
values
(%s, %s, %s);'''
record = (data['username'], data['email'], hashed_password)
# 3. 커서를 가져온다.
cursor = connection.cursor()
# 4. 쿼리문을 커서를 이용해서 실행한다.
cursor.execute(query, record)
# 5. 커넥션을 커밋해줘야 한다 => DB에 영구적으로 반영하라는 뜻
connection.commit()
# 5-1. DB에 저장된 user_id 값 가져오기.
user_id = cursor.lastrowid
# 6. 자원 해제
cursor.close()
connection.close()
except mysql.connector.Error as e :
print(e)
cursor.close()
connection.close()
return {'error' : str(e)}, 503
# user_id 값을 바로 보내면 안되고,
# JWT 로 암호화 해서 보내준다.
# 암호화를 하는 방법
access_token = create_access_token(user_id)
return {'result' : 'success', 'access_token' : access_token}, 200
class UserLoginResource(Resource) :
def post(self) :
# 1. 클라이언트로부터 body로 넘어온 데이터를 받아온다.
# {
# "email" : "abc@naver.com",
# "password" : "1234"
# }
data = request.get_json()
# 2. 이메일로, DB에 해당 이메일과 일치하는 데이터를 가져온다.
try :
connection = get_connection()
query = '''select *
from user
where email = %s;'''
record = (data['email'], )
#select 문은, dictionary = True를 해준다.
cursor = connection.cursor(dictionary = True)
cursor.execute(query, record)
# select 문은, 아래 함수를 이용해서, 데이터를 가져온다.
result_list = cursor.fetchall()
print(result_list)
# 중요, DB에서 가져온 timestamp는
# 파이썬의 datetime 으로 자동 변환된다.
# 문제는, 이 데이터를 json으로 바로 보낼 수 없으므로
# 문자열로 바꾼뒤 저장하여 보낸다.
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 = i + 1
cursor.close()
connection.close()
except mysql.connector.Error as e :
print(e)
cursor.close()
connection.close()
return{'error' : str(e)}, 503
# 3. result_list 의 행의 갯수가 1개이면, 유저 데이터를 정상적으로 받아온 것이고,
# 행의 갯수가 0이면, 클라이언트가 요청한 이메일은 회원가입이 되어 있지 않은 이메일이다.
if len(result_list) != 1 :
return {'ERROR' : '등록되지 않은 이메일입니다.'}, 400
# 4. 비밀번호가 유효한지 확인한다.
user_info = result_list[0]
# data['password'] 와 user_info['password']를 비교한다.
check = check_password(data['password'], user_info['password'])
if check == False :
return {'ERROR' : '비밀번호가 일치하지 않습니다.'}
# 엑세스 토큰요청하고, 해당 엑세스 토큰을 1분후 만료시킨다.
access_token = create_access_token(user_info['id'], expires_delta=datetime.timedelta(minutes=1))
return {'result' : 'success', 'access_token' : access_token}, 200