메뉴 닫기

Flask JWT 인증 API 로그인 갱신 블랙리스트 가드 데코레이터 예제로 배우는 완벽 가이드

Flask JWT 인증 API 로그인 갱신 블랙리스트 가드 데코레이터 예제로 배우는 완벽 가이드

📌 JWT 로그인과 갱신 토큰, 블랙리스트, 가드 데코레이터까지 Flask로 안전한 API를 구현하는 실전 로드맵

비밀번호를 안전하게 검증하고, 엑세스 토큰과 리프레시 토큰을 분리해 운용하며, 탈취된 토큰을 신속히 차단하는 흐름까지 한 번에 잡히면 백엔드 개발이 훨씬 가벼워집니다.
JWT는 무상태 인증을 가능하게 만들지만, 만료 전략과 블랙리스트 처리, 권한 가드가 맞물리지 않으면 보안 구멍이 생기기 쉽습니다.
이 글은 Flask로 로그인, 토큰 갱신, 로그아웃 시 블랙리스트 처리, 그리고 데코레이터 기반의 라우트 보호를 단계별 예제로 정리합니다.
실무에서 바로 붙여 쓸 수 있는 엔드포인트 설계 관점과 보안 체크리스트도 함께 녹여, 최소한의 코드로 최대한의 안전성과 가독성을 갖추는 방법을 소개합니다.

비즈니스 로직은 단순한데 인증이 복잡해 보였다면, 핵심은 규칙을 명확히 하고 코드 구조를 깔끔하게 나누는 데 있습니다.
해싱된 비밀번호 검증, 짧은 수명의 액세스 토큰과 긴 수명의 리프레시 토큰, 서버 측 블랙리스트 저장소, 그리고 권한 레벨을 확인하는 가드 데코레이터가 유기적으로 연결되면 유지보수 난도가 크게 낮아집니다.
아래 목차대로 따라가면 Flask 앱에 표준적인 JWT 인증 흐름을 빠르게 구축하고, 테스트 가능한 형태로 확장할 수 있습니다.
예제 중심으로 설명하되, 보안상 놓치기 쉬운 포인트는 따로 강조해 실수 확률을 줄였습니다.



🔗 Flask JWT 인증 구조 한눈에 보기

JWT(JSON Web Token)는 서버가 사용자의 인증 상태를 클라이언트에게 안전하게 전달하기 위해 사용하는 대표적인 방식입니다.
세션 기반 인증과 달리 서버에 상태 정보를 저장하지 않아도 되므로 확장성이 높고, 마이크로서비스 아키텍처 환경에서 특히 많이 활용됩니다.
하지만 JWT는 발급과 만료 전략, 블랙리스트 처리까지 고려하지 않으면 보안적으로 허점이 생기기 쉽습니다.
따라서 Flask로 구현할 때는 구조를 명확히 설계하는 것이 중요합니다.

Flask 애플리케이션에서 JWT 인증 흐름은 크게 네 가지 요소로 나눌 수 있습니다.
첫째, 로그인 엔드포인트에서 비밀번호를 검증하고 액세스 토큰과 리프레시 토큰을 발급합니다.
둘째, 보호된 API 요청 시 클라이언트가 액세스 토큰을 헤더에 담아 보내고, 서버는 이를 검증합니다.
셋째, 액세스 토큰이 만료되면 리프레시 토큰을 이용해 새로운 토큰을 발급합니다.
넷째, 로그아웃 시에는 해당 토큰을 블랙리스트에 등록해 재사용을 막습니다.

  • 🔑로그인 시 Access TokenRefresh Token을 함께 발급
  • 🛡️API 요청 시 Authorization 헤더에 Bearer 토큰 전달
  • ♻️만료된 Access Token은 Refresh Token으로 재발급
  • 🚫로그아웃 시 해당 토큰을 블랙리스트에 등록하여 무효화

이 네 가지 단계를 기반으로 Flask 앱에서 JWT 인증을 구현하면, 사용자는 로그인 후 제한된 시간 동안만 API에 접근할 수 있으며, 보안이 필요한 엔드포인트는 가드 데코레이터를 통해 손쉽게 보호할 수 있습니다.
또한 Redis나 데이터베이스에 블랙리스트를 저장하면, 탈취된 토큰이더라도 더 이상 유효하지 않도록 제어할 수 있습니다.

💎 핵심 포인트:
Flask에서 JWT 인증을 설계할 때는 단순한 로그인/검증을 넘어, 토큰 갱신과 블랙리스트 관리, 데코레이터 기반 라우트 보호까지 고려해야 실무에서 안전하게 활용할 수 있습니다.

🛠️ 로그인 API 구현과 비밀번호 검증

Flask에서 JWT 인증을 구현할 때 가장 먼저 작성해야 할 부분이 로그인 API입니다.
사용자가 입력한 이메일과 비밀번호를 서버에서 검증한 후, 조건이 충족되면 Access Token과 Refresh Token을 발급하는 구조입니다.
이때 중요한 점은 비밀번호를 평문으로 저장하거나 비교해서는 안 되고, 반드시 해싱 처리를 통해 안전하게 관리해야 한다는 것입니다.

파이썬에서는 Werkzeug 라이브러리의 generate_password_hashcheck_password_hash 함수를 자주 사용합니다.
회원가입 시 비밀번호를 해싱하여 저장하고, 로그인 요청 시에는 입력값을 해싱된 값과 비교하는 방식입니다.
이 과정을 통과하면 JWT 토큰을 생성하고 클라이언트에게 응답으로 반환합니다.

CODE BLOCK
from flask import Flask, request, jsonify
from werkzeug.security import check_password_hash
import jwt, datetime

app = Flask(__name__)
app.config['SECRET_KEY'] = "your_secret_key"

@app.route('/login', methods=['POST'])
def login():
    data = request.get_json()
    user = find_user_by_email(data['email'])  # DB 조회
    
    if not user or not check_password_hash(user.password, data['password']):
        return jsonify({"msg": "Invalid credentials"}), 401
    
    access_token = jwt.encode(
        {"user_id": user.id, "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=15)},
        app.config['SECRET_KEY'],
        algorithm="HS256"
    )
    
    refresh_token = jwt.encode(
        {"user_id": user.id, "exp": datetime.datetime.utcnow() + datetime.timedelta(days=7)},
        app.config['SECRET_KEY'],
        algorithm="HS256"
    )
    
    return jsonify({"access_token": access_token, "refresh_token": refresh_token}), 200

위 예시는 기본적인 로그인 API 구조입니다.
사용자가 올바른 자격 증명을 제공하면 서버는 짧은 수명의 Access Token과 긴 수명의 Refresh Token을 함께 발급합니다.
이후 클라이언트는 요청마다 Access Token을 사용하고, 만료되면 Refresh Token으로 새로운 토큰을 받아야 합니다.

⚠️ 주의: 비밀번호를 평문으로 저장하거나 단순 MD5 같은 약한 알고리즘으로 해싱하는 것은 심각한 보안 위험을 초래합니다.
반드시 bcrypt 또는 pbkdf2_hmac 같은 강력한 알고리즘을 사용하세요.



⚙️ 토큰 갱신 Refresh와 만료 전략

JWT 인증의 핵심은 Access TokenRefresh Token을 분리해 관리하는 것입니다.
Access Token은 일반적으로 15분에서 30분 정도의 짧은 수명을 가지며, 이 기간이 지나면 만료됩니다.
반면 Refresh Token은 7일에서 14일 또는 그 이상의 기간을 설정해 사용자가 계속 인증 상태를 유지할 수 있도록 돕습니다.

만약 Access Token이 만료되면 클라이언트는 Refresh Token을 이용해 새로운 Access Token을 발급받습니다.
이 과정을 통해 보안성과 편의성을 동시에 확보할 수 있습니다.
만약 Refresh Token마저 만료되면 사용자는 다시 로그인해야 하며, 이를 통해 장기간 세션 유지에 따른 위험을 줄일 수 있습니다.

CODE BLOCK
@app.route('/refresh', methods=['POST'])
def refresh():
    data = request.get_json()
    refresh_token = data.get("refresh_token")
    
    try:
        payload = jwt.decode(refresh_token, app.config['SECRET_KEY'], algorithms=["HS256"])
    except jwt.ExpiredSignatureError:
        return jsonify({"msg": "Refresh token expired"}), 401
    except jwt.InvalidTokenError:
        return jsonify({"msg": "Invalid token"}), 401
    
    new_access_token = jwt.encode(
        {"user_id": payload["user_id"], "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=15)},
        app.config['SECRET_KEY'],
        algorithm="HS256"
    )
    
    return jsonify({"access_token": new_access_token}), 200

위 코드처럼 Refresh Token을 검증한 후 새로운 Access Token을 발급하는 API를 구성할 수 있습니다.
이때 Refresh Token이 만료되었다면 반드시 새 로그인을 요구해야 하며, 그렇지 않으면 탈취된 토큰으로 장기간 악용될 수 있습니다.

💬 Access Token은 짧게, Refresh Token은 길게 설정하되, 서버에서는 블랙리스트 기능을 함께 운영하는 것이 안전합니다.

🔌 블랙리스트 로그아웃과 토큰 무효화

JWT는 기본적으로 stateless 구조이기 때문에 서버가 이미 발급한 토큰을 직접 만료시킬 수 없습니다.
즉, 클라이언트가 보유한 토큰은 만료 시간까지 계속 유효하게 작동합니다.
이 문제를 해결하기 위해 사용하는 방식이 바로 블랙리스트입니다.
로그아웃 시 해당 토큰을 블랙리스트에 저장하고, 이후 요청에서 해당 토큰이 블랙리스트에 있으면 인증을 거부하는 방식입니다.

블랙리스트는 데이터베이스나 Redis 같은 인메모리 저장소에 보관할 수 있습니다.
Redis를 사용하면 속도가 빠르고 만료 시간을 함께 지정할 수 있어 토큰의 만료 시점과 동기화가 가능합니다.

CODE BLOCK
blacklist = set()

@app.route('/logout', methods=['POST'])
def logout():
    token = request.headers.get("Authorization").split()[1]
    blacklist.add(token)
    return jsonify({"msg": "Successfully logged out"}), 200

@app.before_request
def check_blacklist():
    auth_header = request.headers.get("Authorization")
    if auth_header:
        token = auth_header.split()[1]
        if token in blacklist:
            return jsonify({"msg": "Token has been revoked"}), 401

위 예시는 파이썬의 set()을 사용한 간단한 블랙리스트 예제입니다.
실제 서비스에서는 Redis나 데이터베이스를 이용해 토큰을 저장하고 만료 시간을 설정하는 것이 일반적입니다.
이렇게 하면 로그아웃한 사용자가 다시 토큰을 사용하지 못하도록 안전하게 차단할 수 있습니다.

💡 TIP: 블랙리스트를 적용할 때는 토큰의 jti (JWT ID) 클레임을 함께 저장하면 토큰을 더 효율적으로 관리할 수 있습니다.



💡 가드 데코레이터로 라우트 보호하기

JWT 인증이 제대로 동작하려면 민감한 API 엔드포인트를 가드 데코레이터로 보호해야 합니다.
이 데코레이터는 요청 헤더에 포함된 JWT 토큰을 검증하고, 유효하지 않으면 접근을 차단하는 역할을 합니다.
Flask에서는 데코레이터를 통해 중복 코드를 줄이고, 인증이 필요한 라우트와 그렇지 않은 라우트를 명확하게 구분할 수 있습니다.

가드 데코레이터는 단순히 토큰의 유효성만 확인하는 것이 아니라, 블랙리스트 여부까지 검증해야 합니다.
또한 사용자의 권한(Role)에 따라 접근을 제한할 수도 있습니다.
예를 들어 관리자는 모든 API를 사용할 수 있지만, 일반 사용자는 특정 API만 호출하도록 제어할 수 있습니다.

CODE BLOCK
from functools import wraps

def token_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth_header = request.headers.get("Authorization")
        if not auth_header:
            return jsonify({"msg": "Token is missing"}), 401
        
        token = auth_header.split()[1]
        if token in blacklist:
            return jsonify({"msg": "Token revoked"}), 401
        
        try:
            payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"])
        except jwt.ExpiredSignatureError:
            return jsonify({"msg": "Token expired"}), 401
        except jwt.InvalidTokenError:
            return jsonify({"msg": "Invalid token"}), 401
        
        return f(payload["user_id"], *args, **kwargs)
    return decorated

@app.route('/protected', methods=['GET'])
@token_required
def protected_route(user_id):
    return jsonify({"msg": f"Hello user {user_id}, this is a protected route"}), 200

이 데코레이터를 사용하면 인증이 필요한 모든 엔드포인트에 @token_required를 붙여 간단히 보호할 수 있습니다.
또한 user_idrole 값을 함께 전달해 권한 기반 제어까지 확장 가능합니다.

⚠️ 주의: 토큰 검증 로직을 엔드포인트마다 직접 작성하면 유지보수가 어렵습니다.
데코레이터를 사용하면 중복을 줄이고, 보안 정책을 일관되게 적용할 수 있습니다.

자주 묻는 질문 FAQ

JWT와 세션 기반 인증의 차이는 무엇인가요?
세션 인증은 서버에 상태 정보를 저장해야 하지만, JWT는 무상태 방식이라 확장성과 분산 처리에 유리합니다.
Access Token과 Refresh Token은 꼭 나눠야 하나요?
네, 보안을 위해 분리하는 것이 필수적입니다. Access Token은 짧게, Refresh Token은 길게 설정해 리스크를 최소화해야 합니다.
JWT를 어디에 저장하는 것이 안전할까요?
보통은 브라우저의 HttpOnly 쿠키나 보안이 강화된 저장소를 사용합니다. LocalStorage는 XSS 공격에 취약할 수 있습니다.
블랙리스트는 꼭 필요한가요?
로그아웃 기능이나 토큰 탈취 상황을 고려하면 필수적입니다. 그렇지 않으면 만료 전까지 토큰이 계속 유효하게 남습니다.
JWT 크기가 커지면 성능에 영향이 있나요?
네, 토큰에 너무 많은 클레임을 담으면 네트워크 오버헤드가 발생합니다. 최소한의 정보만 포함하는 것이 좋습니다.
토큰 갱신 API는 어디에 두는 것이 좋을까요?
별도의 엔드포인트로 분리하는 것이 일반적입니다. 예를 들어 /refresh와 같이 운영하면 관리가 편리합니다.
JWT는 암호화된 토큰인가요?
JWT는 기본적으로 인코딩된 형태이며, 암호화가 아닙니다. 민감한 정보를 직접 담지 않는 것이 안전합니다.
가드 데코레이터는 모든 엔드포인트에 적용해야 하나요?
민감한 API 엔드포인트에만 적용하면 됩니다. 예를 들어 공개 API에는 불필요하지만, 사용자 정보나 결제 관련 API에는 필수입니다.

📌 Flask JWT 인증 API 핵심 정리

Flask로 JWT 인증 API를 구현할 때는 로그인, 토큰 갱신, 로그아웃과 블랙리스트, 그리고 가드 데코레이터까지 유기적으로 연결해야 안전하고 유지보수하기 좋은 구조가 완성됩니다.
로그인 시 발급하는 Access Token과 Refresh Token의 만료 전략을 분리해 두면 보안성과 편의성을 동시에 확보할 수 있습니다.
또한 블랙리스트를 통해 탈취된 토큰이나 로그아웃된 토큰을 무효화해야 하며, 데코레이터를 이용해 민감한 API 엔드포인트를 일관성 있게 보호할 수 있습니다.
이 과정을 지키면 확장 가능한 인증 시스템을 쉽게 구축할 수 있고, 실무에서도 안전한 API 서비스를 운영할 수 있습니다.


🏷️ 관련 태그 : Flask, JWT인증, 파이썬백엔드, API보안, 로그인API, RefreshToken, AccessToken, 블랙리스트, 데코레이터, 웹개발