[Flask] Flask-JWT-Extended

Alexandria·2023년 11월 20일
1

Python3 Flask

목록 보기
10/14
post-thumbnail

1. Flask-JWT-Extended

Flask 프레임워크에서 JWT를 사용하여 인증 시스템을 쉽게 구현할 수 있도록 도와주는 패키지입니다.

  1. JWT 생성 및 관리
    JWT 토큰을 생성하고 관리하는 기능을 제공합니다. 사용자 정보와 함께 토큰을 생성하고, 토큰에 필요한 클레임(claim)을 추가할 수 있습니다.

  2. 토큰 검증 및 해독
    클라이언트가 제공한 토큰을 검증하고, 토큰이 유효한지 확인하여 인증을 처리합니다. 또한, 토큰 안에 포함된 정보를 해독하여 사용자 정보를 얻을 수 있습니다.

  3. JWT Refresh
    JWT의 유효 기간이 지나면 자동으로 재인증을 하기 위해 리프레시 토큰을 사용할 수 있습니다.

  4. 보호된 엔드포인트 처리
    특정 API 엔드포인트에 보안을 적용하고, JWT 토큰이 필요한 보호된 엔드포인트를 정의할 수 있습니다.

  5. 페이로드 추가
    JWT의 페이로드에 추가 정보를 넣어 토큰을 발행할 수 있습니다. 사용자 ID, 권한 등의 정보를 토큰에 추가하여 전달할 수 있습니다.

  6. 기본적인 JWT 기능 지원
    Flask-JWT-Extended는 기본적인 JWT 기능들을 확장하고, 편리한 사용 방법을 제공합니다.

💡 본 글은 예제 코드를 이용하여 설명합니다.

2. JWT (Json Web Token)

JWT는 웹 애플리케이션에서 사용자의 인증 정보를 안전하게 전달하기 위해 사용되는 토큰 기반의 인증 방식이며, JSON 형식으로 정보를 인코딩하고, 서명을 사용하여 데이터의 무결성을 보장합니다.

JWT는 다음과 같이 세 부분의 부분으로 구성됩니다.

  • Header : JWT의 유형과 사용하는 암호화 알고리즘 등의 메타 정보를 담고 있습니다.

  • Payload : 일련의 클레임(Claim) 정보를 담고 있습니다. JWT 사양은 토큰에 일반적으로 포함되는 표준 필드인 7개의 등록된 클레임(Registered Claim Names)을 정의되며,토큰의 목적에 따라 사용자 지정 클레임 또한 일반적으로 포함됩니다.

  • Signature : 헤더와 내용을 합친 후, 지정된 암호화 알고리즘과 비밀 키를 사용하여 생성한 서명입니다. 서버 측에서 유효성을 검증할 때 사용되며, 서명이 올바른지 확인하여 JWT의 변조 여부를 판단합니다.

2.1. 등록된 클레임

특정 목적을 갖는 미리 정의된 클레임(claim)들을 말합니다.

이러한 클레임들은 표준적으로 정의되어 있으며, 특정 목적에 맞게 사용됩니다.

ClaimDescription
iss토큰을 발급한 발행자(Issuer)의 식별자
sub토큰이 표현하고 있는 대상(Subject)을 표현
aud토큰의 수신자(Audience)
exp토큰의 만료 시간(Expiration Time)
nbf토큰의 유효 시작 시간(Not Before)
iat토큰이 발급된 시간(Issued At)
jti토큰의 고유 식별자(JWT ID)

2.2. 토큰 종류

JWT은 리소스 사용에 필요한 access_token과 토큰 재발급에 사용되는 refresh_token을 사용합니다.

TokenDescription
access_token사용자의 인증 정보를 나타내는 토큰으로, 짧은 유효기간을 가짐
refresh_tokenaccess_token을 갱신하는데 사용하는 토큰으로, 긴 유효기간을 가짐

3. 설정

flask/source/config.py를 살펴보면 JWT와 관련된 설정을 적용하였습니다.

JWT는 HTTP의 headers에 위치하게 하여 Authorization 필드에 Bearer {JWT} 형식을 요구하게 됩니다.

class Config:
    # 코드 생략
    
    JWT_TOKEN_LOCATION = ["headers"]
    JWT_ACCESS_TOKEN_EXPIRES = datetime.timedelta(minutes=15)
    JWT_REFRESH_TOKEN_EXPIRES = datetime.timedelta(days=7)

flask/source/my_app/__init__.py를 살펴보면 Flask-JWT-Extended의 JWTManager 객체를 생성하였습니다. 이후 JWT매니저 객체를 초기화해줍니다.

로그인되어 있지 않은 상태에서 로그인이 되어야 하는 페이지에 접근 시, 로그인 페이지(index.login)로 리다이렉트 됩니다.

jwt     = JWTManager()
# 코드 생략
def create_app():
    app.config.from_object(obj=config["development"])

    with app.app_context():
    	# 코드 생략
    
    	jwt.init_app(app=app)

4. 토큰 발급

flask/source/my_app/api/v1/auth.py를 살펴보면 JWT를 발급하는 내용입니다.

create_access_token()create_refresh_token()을 이용하여 access_token과 refresh_token을 생성할 수 있습니다.

identity 인자에 주어지는 값은 JWT의 sub 클레임에 저장이 됩니다.

if user and user.check_password(password=args.get("password")):
	access_token    = create_access_token(identity=user.id)
	refresh_token   = create_refresh_token(identity=user.id)
	return make_response(jsonify(access_token=access_token, refresh_token=refresh_token), 201)

반환된 토큰은 다음과 같은 형식입니다.

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcwMjgxNDg0OCwianRpIjoiMGQyNWExODQtODZiNS00OWVjLWJiYzQtN2Q4NDI5NmQwYmEyIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6MSwibmJmIjoxNzAyODE0ODQ4LCJleHAiOjE3MDM0MTk2NDh9.BLNnCYtZvKhZICNb4fNixt9LAfMyTe4lV3Cx-S0KJVQ",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcwMjgxNDg0OCwianRpIjoiYTczNmIyZDktYjhmYi00ZTNhLTg3NDctNzQ3MzY4NjU0M2UyIiwidHlwZSI6InJlZnJlc2giLCJzdWIiOjEsIm5iZiI6MTcwMjgxNDg0OCwiZXhwIjoxNzAzNDE5NjQ4fQ.fER6Rtj8ypkg8tmWhKJfKD5y6c28x2JMuzm94ul3sLQ"
}

💡 JWT Debugger의 Encoded 부분에 생성된 토큰을 넣으면 sub을 확인 할 수 있습니다.

5. 토큰 사용

flask/source/my_app/api/v1/auth.py를 살펴봅니다.

토큰을 요구하고 싶으면 @jwt_required()라는 데코레이터를 사용하면 됩니다.

토큰의 무결성 검증이 통과된다면 get_jwt_identity()를 이용하여 전송된 access_token의 sub 클레임의 값을 얻을 수 있습니다.

@jwt_required()
def get(self):
	"""사용자 조회"""

	user = User.query.get(ident=get_jwt_identity())
	user = user.serialize()
	return make_response(dumps(obj=user, ensure_ascii=False, indent=4), 200)

6. 토큰 갱신

flask/source/my_app/api/v1/auth.py를 살펴봅니다.

access_token이 필요하면 @jwt_required()만 사용하면 됩니다. 하지만 refresh_token이 필요하다면 refresh 인자에 True를 전달하면 됩니다.

토큰의 무결성 검증이 통과된다면 get_jwt_identity()를 이용하여 전송된 refresh_token의 sub 클레임의 값을 얻을 수 있습니다.

@jwt_required(refresh=True)
def patch(self):
	"""토큰 갱신"""
        
	access_token = create_access_token(identity=get_jwt_identity())
	return make_response(jsonify(access_token=access_token), 201)

7. 토큰 폐기

flask/source/my_app/api/v1/auth.py를 살펴봅니다.

access_token의 재사용을 금지하기 위해서는 DataBase, Redis 혹은 파일과 같은 시스템에 폐기될 토큰 식별 값들을 저장하고 관리하면 됩니다.

토큰의 식별 값을 얻기 위해서는 get_jwt()를 호출하여 여러 클레임 중 jti를 가져오면 됩니다.

def delete(self):
	"""토큰 폐기"""

	rc.set(name=get_jwt().get("jti"), value="")
	return "", 204

token_in_blocklist_loader 데코레이터를 사용하면 토큰이 재사용 되었는 지 검사할 수 있는 방법을 사용자가 정의할 수 있습니다.

flask/source/my_app/__init__.py를 살펴보면 token_in_blocklist_loader를 이용한 폐기된 토큰을 저장하는 코드입니다.

반환 값이 True이면 사용이 금지된 토큰임을 의미하며, False이면 사용이 가능한 토큰임을 의미합니다.

@jwt.token_in_blocklist_loader
def check_if_token_revoked(jwt_header, jwt_payload: dict) -> bool:
    """
    사용된 액세스 토큰(access_token)의 재사용을 방지합니다.
    """

    if not jwt_payload and "jti" not in jwt_payload:
        return None
    
    jti = jwt_payload.get("jti")
    token = rc.get(name=jti)
    return token is not None
profile
IT 도서관

0개의 댓글