Access Token을 생성하는 방법은 여러 가지가 있는데, 가장 널리 사용되는 기술은 중 하나가 바로 JWT(JSON Web Tokens)입니다. JSON Web Token은 이름 그대로 JSON 데이터를 token으로 변환하는 방식입니다. Flask에서 이를 이용하여 웹 토큰 인증을 하는 것을 다루어 보겠습니다.
예제에서는 PyJWT 1.4.0 버전과 bcrypt 3.2.0 버전을 이용합니다.
pip3 install PyJWT==1.4.0
pip3 install bcrypt==3.2.0
Access Token 인증 과정은 다음과 같습니다.
1. 토큰 생성
2. 토큰 사용
예시 코드
로그인 시 인증 확인 후 Access 토큰 생성 (Flask router)
@bp.route('/login', methods=['POST'])
def login():
credential = request.json
email = credential['email'] # 요청한 이메일
password = credential['password'] # 요청한 비밀번호
row = user.get_user_from_email(email) # 이메일을 이용하여 실제 유저 정보를 가져옴
# 요청한 이메일의 유저 정보가 있는 경우, 비밀번호를 대조하여 확인
if row and bcrypt.checkpw(password.encode('UTF-8'), row['hashed_password'].encode('UTF-8')):
user_id = row['id']
payload = {
'user_id': user_id, # user id
'exp': datetime.utcnow() + timedelta(seconds=60 * 60 * 24) # 만료 시간(24시간 후 )
}
# 비밀번호가 일치하는 경우 JWT 생성
token = jwt.encode(payload, current_app.config['JWT_SECRET_KEY'], 'HS256')
return jsonify({
'access_token': token.decode('UTF-8')
})
else:
# 유저 정보가 없거나 비밀번호가 일치하지 않는 경우 401 코드 반환
return '', 401
/login 엔드포인트에 요청 시 유저의 이메일과 비밀번호를 확인 후 JWT Access Token을 생성합니다. 이 때 Payload에는 user id와 토큰 만료 시간(exp)를 저장하였습니다. 토큰 만료 시간은 토큰 생성 시간으로 부터 24시간 후 입니다. payload와 app에 저장된 'JWT_SECRET_KEY'를 이용하여 token을 생성합니다. ('JWT_SECRET_KEY'는 따로 설정을 해주어야 합니다.)
그리고 access_token을 반환하면 frontend(login.js)에서는 다음과 같이 쿠키에 토큰을 저장하면 됩니다.
function createCookie(value) {
var now = new Date();
var expirationDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 7, 0, 0, 0);
document.cookie = 'token=' + value + '; expires=' + expirationDate + '; path=/';
};
$.ajax({
method: "POST",
url: "http://localhost:5000/user/login",
data: JSON.stringify({
"email": id,
"password": password
}),
contentType: 'application/json'
})
.done(function (msg) {
// 요청 전송 후 토큰이 존재하는 경우 토큰을 쿠키에 저장함
if (msg.access_token) {
createCookie(msg.access_token);
}
});
엔드포인트 요청 시 Access Token 확인(Flask Router, decorator 함수)
from functools import wraps
from flask import request, Response, current_app, g
from .user import get_user_from_id
# token을 decode하여 반환함, decode에 실패하는 경우 payload = None으로 반환
def check_access_token(access_token):
try:
payload = jwt.decode(access_token, current_app.config['JWT_SECRET_KEY'], "HS256")
if payload['exp'] < datetime.utcnow(): # 토큰이 만료된 경우
payload = None
except jwt.InvalidTokenError:
payload = None
return payload
# decorator 함수
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwagrs):
access_token = request.headers.get('Authorization') # 요청의 토큰 정보를 받아옴
if access_token is not None: # 토큰이 있는 경우
payload = check_access_token(access_token) # 토큰 유효성 확인
if payload is None: # 토큰 decode 실패 시 401 반환
return Response(status=401)
else: # 토큰이 없는 경우 401 반환
return Response(status=401)
return f(*args, **kwagrs)
return decorated_function
login_required라고 하는 데코레이트 함수를 생성하였습니다. 요청 정보에 포함된 토큰 정보를 확인하여 로그인된 상태인지 아닌지를 확인합니다.
check_access_token() 함수에서는 JWT를 decode하여 기존의 payload를 가져옵니다. 이 때 부적절한 토큰의 경우 InvalidTokenError가 발생하게 되고 None을 반환합니다. 만약 토큰이 정확하게 decode 되었지만 토큰 유효 시간이 만료된 경우에도 None을 반환합니다.
login_required 함수에서는 토큰 인증에 실패한 경우 401 코드를 반환하고, 인증에 성공한 경우 decorate로 감싸진 함수를 실행합니다. 다음과 같이 login_required 데코레이터를 활용하면 됩니다.
from services.auth import login_required
@bp.route('/action', methods=['POST'])
@login_required # 로그인이 필요한 엔드포인트의 경우 데코레이터 추가
def action():
""" 요청에 맞는적절한 Action """
return '', 200
로그인 인증을 해야하는 함수에 @login_required만 추가하게되면 토큰 인증을 하도록 쉽게 구성할 수 있습니다.
Flask에서 JWT access token 인증하는 방법에 대하여 다루어보았습니다.
'About > Flask' 카테고리의 다른 글
[Backend] 백엔드 API 패턴 - 레이어드 아키텍처 패턴(layered architecture) (2) | 2022.02.04 |
---|---|
[RabbitMQ] Python에서 Pika를 이용한 RabbitMQ 사용 (Topic Queue) (0) | 2022.01.05 |
[Flask] 비동기 호출(태스크 큐, 토픽 큐, RabbitMQ) (0) | 2022.01.04 |
[Flask] Session 객체를 이용한 동기식 호출 (0) | 2021.12.31 |
[Flask] Flask-SQLAlchemy 사용해보기 (1) | 2021.12.30 |