Flask 프레임워크에서 JWT
를 사용하여 인증 시스템을 쉽게 구현할 수 있도록 도와주는 패키지입니다.
JWT 생성 및 관리
JWT 토큰을 생성하고 관리하는 기능을 제공합니다. 사용자 정보와 함께 토큰을 생성하고, 토큰에 필요한 클레임(claim)을 추가할 수 있습니다.
토큰 검증 및 해독
클라이언트가 제공한 토큰을 검증하고, 토큰이 유효한지 확인하여 인증을 처리합니다. 또한, 토큰 안에 포함된 정보를 해독하여 사용자 정보를 얻을 수 있습니다.
JWT Refresh
JWT의 유효 기간이 지나면 자동으로 재인증을 하기 위해 리프레시 토큰을 사용할 수 있습니다.
보호된 엔드포인트 처리
특정 API 엔드포인트에 보안을 적용하고, JWT 토큰이 필요한 보호된 엔드포인트를 정의할 수 있습니다.
페이로드 추가
JWT의 페이로드에 추가 정보를 넣어 토큰을 발행할 수 있습니다. 사용자 ID, 권한 등의 정보를 토큰에 추가하여 전달할 수 있습니다.
기본적인 JWT 기능 지원
Flask-JWT-Extended는 기본적인 JWT 기능들을 확장하고, 편리한 사용 방법을 제공합니다.
💡 본 글은 예제 코드를 이용하여 설명합니다.
JWT는 웹 애플리케이션에서 사용자의 인증 정보를 안전하게 전달하기 위해 사용되는 토큰 기반의 인증 방식이며, JSON 형식으로 정보를 인코딩하고, 서명을 사용하여 데이터의 무결성을 보장합니다.
JWT는 다음과 같이 세 부분의 부분으로 구성됩니다.
Header : JWT의 유형과 사용하는 암호화 알고리즘 등의 메타 정보를 담고 있습니다.
Payload : 일련의 클레임(Claim) 정보를 담고 있습니다. JWT 사양은 토큰에 일반적으로 포함되는 표준 필드인 7개의 등록된 클레임(Registered Claim Names)을 정의되며,토큰의 목적에 따라 사용자 지정 클레임 또한 일반적으로 포함됩니다.
Signature : 헤더와 내용을 합친 후, 지정된 암호화 알고리즘과 비밀 키를 사용하여 생성한 서명입니다. 서버 측에서 유효성을 검증할 때 사용되며, 서명이 올바른지 확인하여 JWT의 변조 여부를 판단합니다.
특정 목적을 갖는 미리 정의된 클레임(claim)들을 말합니다.
이러한 클레임들은 표준적으로 정의되어 있으며, 특정 목적에 맞게 사용됩니다.
Claim | Description |
---|---|
iss | 토큰을 발급한 발행자(Issuer)의 식별자 |
sub | 토큰이 표현하고 있는 대상(Subject)을 표현 |
aud | 토큰의 수신자(Audience) |
exp | 토큰의 만료 시간(Expiration Time) |
nbf | 토큰의 유효 시작 시간(Not Before) |
iat | 토큰이 발급된 시간(Issued At) |
jti | 토큰의 고유 식별자(JWT ID) |
JWT은 리소스 사용에 필요한 access_token
과 토큰 재발급에 사용되는 refresh_token
을 사용합니다.
Token | Description |
---|---|
access_token | 사용자의 인증 정보를 나타내는 토큰으로, 짧은 유효기간을 가짐 |
refresh_token | access_token을 갱신하는데 사용하는 토큰으로, 긴 유효기간을 가짐 |
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)
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을 확인 할 수 있습니다.
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)
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)
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