본문 바로가기

About/Flask

[Flask] JWT access token 인증(로그인 토큰 인증)

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 인증하는 방법에 대하여 다루어보았습니다.