기술과 산업/언어 및 프레임워크

FastAPI 시리즈 12화 - JWT 기반 인증 시스템 완성: 토큰 구조와 보안 처리 심화

B컷개발자 2025. 5. 30. 01:20
728x90

FastAPI에서 JWT(Json Web Token)의 구조, 만료 처리, 리프레시 토큰 전략, 서명 보안 방식 등을 실전 중심으로 다룹니다. 인증 시스템을 안전하게 완성하는 방법을 소개합니다.

 

왜 JWT 구조를 이해해야 하는가?

 

JWT(Json Web Token)는 다음과 같은 구조로 구성됩니다:

HEADER.PAYLOAD.SIGNATURE

파트설명

Header 알고리즘 및 타입 (alg, typ)
Payload 사용자 데이터 (sub, exp, role 등)
Signature 서버 비밀키로 서명된 무결성 보장 부분

JWT는 읽을 수 있지만 조작할 수 없어야 합니다.

따라서 서명(Signature)을 검증하지 않으면, 누구나 위조된 토큰을 만들 수 있게 됩니다.

 


 

1. JWT 유효성 검증 강화

from jose import jwt, JWTError
from fastapi import HTTPException

def verify_token(token: str):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except JWTError:
        raise HTTPException(status_code=401, detail="Invalid token")

 

  • jwt.decode()는 signature 검증 + 만료 확인까지 자동 수행
  • JWTError로 모든 오류 포착 가능
  • signature가 일치하지 않으면 인증 실패

 


 

2. 토큰 만료 시간 처리 (exp)

from datetime import datetime, timedelta

def create_access_token(data: dict, expires_delta: timedelta = None):
    to_encode = data.copy()
    expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

 

  • exp 필드는 ISO UTC 기준으로 입력
  • Swagger에서 만료된 토큰은 즉시 401 반환됨

 


 

3. 리프레시 토큰 전략 설계

 

 

📌 왜 필요한가?

 

  • 액세스 토큰은 수명이 짧아야 보안에 유리 (15~30분)
  • 매번 로그인할 수 없기 때문에 장기 보관용 리프레시 토큰이 필요

 

 

📌 리프레시 토큰 흐름

1. 로그인 성공 → access_token + refresh_token 발급
2. access_token 만료 시 → refresh_token으로 재발급 요청
3. refresh_token은 DB나 Redis에서 상태 관리

 


 

4. 리프레시 토큰 발급 예제

def create_refresh_token(data: dict):
    expire = datetime.utcnow() + timedelta(days=7)
    data.update({"exp": expire})
    return jwt.encode(data, SECRET_KEY, algorithm=ALGORITHM)
@app.post("/token/refresh")
def refresh_token(refresh_token: str):
    try:
        payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=[ALGORITHM])
        username = payload.get("sub")
        # 추가 검증: DB에서 refresh_token 유효성 확인 필요
        new_access = create_access_token({"sub": username})
        return {"access_token": new_access}
    except JWTError:
        raise HTTPException(status_code=401, detail="Invalid refresh token")

 


 

5. 실무 보안 강화 포인트

항목설명

비밀번호 해싱 bcrypt 활용 (pip install passlib[bcrypt])
토큰 블랙리스트 로그아웃 시 refresh_token 무효화 (DB나 Redis 활용)
HTTPS 적용 토큰 유출 방지를 위해 항상 TLS 사용
secure, httponly 쿠키 토큰을 쿠키에 저장 시 필수 설정
사용자 역할(Role) 기반 제어 JWT Payload에 role 포함 후 접근 제어 적용

 


 

6. 사용자 권한(Role) 기반 API 접근 제어

def get_admin_user(token: str = Depends(oauth2_scheme)):
    payload = verify_token(token)
    if payload.get("role") != "admin":
        raise HTTPException(status_code=403, detail="Admins only")
    return payload

@app.get("/admin/dashboard")
def read_admin_dashboard(user=Depends(get_admin_user)):
    return {"message": f"Welcome {user['sub']}, to the admin dashboard"}

 

  • JWT에 사용자 role 정보를 포함시키고
  • 인증 함수에서 권한 확인 로직을 분리

 


 

FastAPI 인증 시스템을 진짜 서비스 수준으로 완성하려면

항목적용 방법

JWT 발급 sub, exp 포함 후 서명
토큰 만료 timedelta 기반 설정
리프레시 토큰 별도 토큰 발급 + 상태 관리
사용자 권한 제어 JWT에 role 필드 포함
보안 강화 HTTPS, bcrypt, 토큰 저장소, 쿠키 옵션 등 적용 필요

FastAPI는 인증을 “쉽게 시작할 수 있으면서도, 깊이 있게 확장 가능한 구조”로 설계되어 있습니다.

이제 로그인 기능을 넘어서 서비스 전반의 인증/보안 흐름을 스스로 구성할 수 있는 수준까지 도달한 것입니다.

 


 

다음 글 예고

 

FastAPI 시리즈 13화 - 사용자 권한(Role) 관리와 종속성 적용 방법

에서는 사용자 계층별 접근 제어를 어떻게 분리하고, Depends를 활용해 권한 시스템을 구현하는지를 설명합니다.

728x90