FastAPI에서 JWT 토큰을 이용한 로그인 인증 구현
1. JWT란?
JWT(Json Web Token)는 서버가 사용자 인증을 위해 발급하는 토큰으로, 사용자 정보를 암호화하여 클라이언트에 전달하는 방식입니다.
클라이언트는 이 토큰을 Authorization: Bearer <토큰>
형태로 서버에 전달하고, 서버는 토큰을 검증하여 인증 여부를 판단합니다.
JWT는 다음의 3부분으로 구성됩니다.
- Header: 알고리즘, 토큰 타입
- Payload: 사용자 정보 및 만료 시간 등 클레임
- Signature: 헤더 + 페이로드를 비밀키로 암호화한 서명
Claim 종류
iat
: 발급 시간exp
: 만료 시간sub
: 사용자 ID 등 식별 정보
- 헤더: 토큰 타입(typ)과 암호화 방법(alg)을 보관하는 곳으로 BASE64로 인코딩
- 페이로드: 다양한 종류의 정보를 담는 곳으로 BASE64로 인코딩
2. 관련 패키지 설치
pip install python-jose[cryptography] passlib[bcrypt] python-multipart
python-jose
: JWT 인코딩 및 디코딩passlib
: 비밀번호 해싱python-multipart
: OAuth2 토큰 전달 시 multipart 처리 지원
3. 비밀번호 해싱 유틸리티
# auth/hash_password.py
from passlib.context import CryptContext
class HashPassword:
def __init__(self):
self.pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def hash_password(self, password: str):
return self.pwd_context.hash(password)
def verify_password(self, plain_password: str, hashed_password: str):
return self.pwd_context.verify(plain_password, hashed_password)
4. 회원가입 / 로그인
# routes/users.py
from fastapi import APIRouter, Depends, HTTPException, status
from sqlmodel import select
from auth.hash_password import HashPassword
from database.connection import get_session
from models.users import User, UserSignIn, UserSignUp
user_router = APIRouter(tags=["User"])
hash_password = HashPassword()
@user_router.post("/signup", status_code=201)
async def sign_new_user(data: UserSignUp, session = Depends(get_session)):
user = session.exec(select(User).where(User.email == data.email)).first()
if user:
raise HTTPException(status_code=409, detail="동일한 사용자가 존재합니다.")
new_user = User(
email=data.email,
password=hash_password.hash_password(data.password),
username=data.username
)
session.add(new_user)
session.commit()
session.refresh(new_user)
return {"message": "사용자 등록이 완료되었습니다.", "user": new_user}
5. .env 환경 변수 설정
# .env
DATABASE_URL=mysql+pymysql://fastapiuser:p%40ssw0rd@localhost:3306/fastapidb
SECRET_KEY=SECRET+FASTAPI:
6. 환경 설정 클래스
# database/connection.py
from pydantic_settings import BaseSettings
from typing import Optional
from sqlmodel import SQLModel, create_engine, Session
class Settings(BaseSettings):
DATABASE_URL: Optional[str]
SECRET_KEY: Optional[str]
class Config:
env_file = ".env"
settings = Settings()
engine_url = create_engine(settings.DATABASE_URL, echo=True)
def conn():
SQLModel.metadata.create_all(engine_url)
def get_session():
with Session(engine_url) as session:
yield session
7. JWT 생성 및 검증
# auth/jwt_handler.py
from time import time
from fastapi import HTTPException, status
from jose import jwt
from database.connection import settings
def create_jwt_token(email: str, user_id: int) -> str:
payload = {
"user": email,
"user_id": user_id,
"iat": time(),
"exp": time() + 3600
}
return jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256")
def verify_jwt_token(token: str):
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
if payload.get("exp") is None or time() > payload["exp"]:
raise HTTPException(status_code=403, detail="Token expired")
return payload
except:
raise HTTPException(status_code=400, detail="Invalid token")
8. 로그인 시 JWT 토큰 발급
# routes/users.py
from auth.jwt_handler import create_jwt_token
@user_router.post("/signin")
async def sign_user_in(data: UserSignIn, session=Depends(get_session)):
user = session.exec(select(User).where(User.email == data.email)).first()
if not user or not hash_password.verify_password(data.password, user.password):
raise HTTPException(status_code=401, detail="이메일 또는 비밀번호가 일치하지 않습니다.")
access_token = create_jwt_token(user.email, user.id)
return {
"message": "로그인 성공",
"user": user.username,
"access_token": access_token
}
9. 인증된 사용자만 접근 가능한 API 구성
# routes/events.py
from auth.authenticate import authenticate
@event_router.post("/", status_code=201)
async def create_event(
data: Event = Body(...),
session = Depends(get_session),
user_id: int = Depends(authenticate) # 인증 사용자
):
event = Event(**data.dict(), user_id=user_id)
session.add(event)
session.commit()
session.refresh(event)
return {"message": "이벤트 등록 완료", "user_id": user_id}
10. Swagger UI에서 토큰 인증 테스트
/users/signin
에서 로그인 → access_token 복사- 상단 Authorize 버튼 클릭
Bearer <access_token>
형식으로 붙여넣기- 인증이 필요한 API 호출 시 자동으로 헤더 포함
11. 전체 인증 흐름 요약
단계 | 설명 |
---|---|
1. 회원가입 | 비밀번호 해싱 후 DB에 저장 |
2. 로그인 | 해시 검증 후 JWT 토큰 발급 |
3. 토큰 전달 | Authorization: Bearer 토큰 헤더로 요청 |
4. 인증 라우터 보호 | Depends(authenticate) 로 인증 강제 |
5. Swagger 테스트 | Authorize 버튼으로 토큰 입력 후 실행 |
※ JWT 토큰 내부 구조 예시
'Python' 카테고리의 다른 글
FastAPI + React로 회원관리 시스템 구축 (MySQL 연동) (0) | 2025.05.21 |
---|---|
React와 Python 연결 (0) | 2025.05.20 |
FastAPI-DB연결 (0) | 2025.05.18 |
FastAPI-CRUD (0) | 2025.05.16 |
Path, Query, Response_model (0) | 2025.05.15 |
FastAPI 백엔드 개발 환경 세팅 & 기본 API 구현 정리 (0) | 2025.05.14 |