FastAPI에서 인증 시스템 아키텍처 설계하기
안녕하세요! 오늘은 FastAPI 프레임워크에서 확장성 있고 유지보수하기 쉬운 인증 시스템을 모듈화하는 방법에 대해 알아보겠습니다. 최근 작성한 FastAPI템플릿 프로젝트에서 만든 세 가지 모듈을 가져와서 Web3 게임 백엔드 프로젝트에서 구현한 구조를 바탕으로 작성했습니다.
Fastapi_Restful_API_Template/fastapi_template/docs/common_modules.md at main · southglory/Fastapi_Restful_API_Template 포트폴리오 프로젝토. Contribute to southglory/Fastapi_Restful_API_Template development by creating an account on GitHub. 포트폴리오 프로젝토. Contribute to southglory/Fastapi_Restful_API_Template development by creating an account on GitHub.
왜 모듈화된 인증 시스템이 필요한가? ** 인증 시스템은 단순히 로그인 처리만 하는 것이 아닙니다.
사용자 인증, 토큰 발급, 권한 검사, 암호화 등 복잡한 기능들이 얽혀 있죠.
이러한 기능들을 한 곳에 모아두면 코드는 금방 복잡해지고, 유지보수가 어려워집니다.
모듈화된 접근법은 단일 책임 원칙을 따라 각 구성 요소가 명확한 하나의 책임만 갖도록 합니다.
이는 코드의 가독성을 높이고, 테스트를 용이하게 하며, 확장성을 극대화합니다. ** **3계층 인증 아키텍처 ** 우리(Mr.Claude and Me)가 구현한 인증 시스템은 세 개의 핵심 모듈로 구성됩니다:
1
2
3
/security → /auth → /dependencies
↓ ↓ ↓
저수준 보안 인증 메커니즘 API 의존성
각 계층은 상위 계층에 서비스를 제공하는 형태로 설계되었습니다.
이런 구조는 의존성의 방향이 한쪽으로만 흐르게 하여, 변경이 다른 부분에 미치는 영향을 최소화합니다.
1. security 모듈: 기반 보안 기능 ** security 모듈은 가장 저수준의 보안 기능을 담당합니다:
1
2
3
4
5
/security
├── security_encryption.py # 데이터 암호화/복호화
├── security_file_encryption.py # 파일 암호화
├── security_hashing.py # 해시 함수
└── security_token.py # 토큰 처리
이 모듈은 비즈니스 로직에 독립적인 순수한 암호화 기능을 제공합니다:
1
2
3
4
5
6
7
8
9
10
11
# security_encryption.py의 예시
class SymmetricEncryption:
def __init__(self, key: bytes):
self.key = key
self.cipher = Fernet(key)
def encrypt(self, data: str) -> str:
return self.cipher.encrypt(data.encode()).decode()
def decrypt(self, encrypted: str) -> str:
return self.cipher.decrypt(encrypted.encode()).decode()
- 외부 의존성이 없어 단독으로 테스트 가능
- 암호화 알고리즘을 교체해도 다른 코드에 영향 없음
- 보안 로직이 애플리케이션 전체에 흩어지지 않고 한 곳에 집중
2. auth 모듈: 인증 메커니즘 ** auth 모듈은 security의 기능을 활용하여 실제 인증 메커니즘을 구현합니다:
1
2
3
4
/auth
├── jwt.py # JWT 토큰 처리
├── password.py # 비밀번호 관리
└── __init__.py # 모듈 초기화
이 모듈에서는 비즈니스에 필요한 구체적인 인증 방식을 구현합니다:
1
2
3
4
5
6
7
8
9
10
11
# jwt.py 예시
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
"""사용자 데이터로부터 JWT 토큰 생성"""
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)
def verify_token(token: str) -> dict:
"""토큰 검증 및 페이로드 반환"""
return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
password.py에서는 비밀번호 관련 기능을 제공합니다:
1
2
3
4
5
6
7
def get_password_hash(password: str) -> str:
"""비밀번호를 안전하게 해싱"""
return pwd_context.hash(password)
def verify_password(plain_password: str, hashed_password: str) -> bool:
"""비밀번호 검증"""
return pwd_context.verify(plain_password, hashed_password)
- 보안 로직과 인증 로직의 명확한 분리
- 토큰 및 비밀번호 관리의 표준화
- 인증 방식 변경 시 한 곳만 수정하면 됨
3. dependencies 모듈: API 의존성 주입 ** dependencies 모듈은 FastAPI의 의존성 주입 시스템과 연결됩니다:
1
2
/dependencies
└── auth.py # 인증 관련 의존성 함수
여기서는 auth 모듈을 활용하여 API 엔드포인트에서 사용할 의존성 함수를 제공합니다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# auth.py 예시
async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)) -> User:
"""현재 인증된 사용자를 반환하는 의존성 함수"""
try:
payload = verify_token(token)
username = payload.get("sub")
if username is None:
raise credentials_exception
except jwt.PyJWTError:
raise credentials_exception
# 사용자 검색
user = db.query(User).filter(User.username == username, User.is_active == True).first()
if user is None:
raise credentials_exception
return user
권한 검사를 위한 팩토리 패턴 구현:
1
2
3
4
5
6
7
def has_permission(required_role: str):
"""특정 역할이 필요한 엔드포인트에 사용"""
async def permission_dependency(current_user=Depends(get_current_user)):
if not hasattr(current_user, "role") or current_user.role != required_role:
raise HTTPException(status_code=403, detail="Permission denied")
return current_user
return permission_dependency
- API 라우트에서 인증 및 권한 검사 로직 제거
- 일관된 권한 부여 정책 유지
- FastAPI 의존성 시스템과의 원활한 통합
실제 API 엔드포인트에서의 사용 ** 이 구조의 진가는 실제 API 엔드포인트에서 드러납니다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@router.get("/users/me", response_model=UserResponse)
async def get_user_profile(
current_user: User = Depends(get_current_user)
):
"""현재 로그인한 사용자 정보 조회"""
return current_user
@router.put("/wallets/{wallet_id}", response_model=WalletResponse)
async def update_wallet(
wallet_id: int,
wallet_data: WalletUpdate,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""지갑 정보 업데이트 (소유자만 가능)"""
wallet = db.query(Wallet).filter(Wallet.id == wallet_id).first()
if not wallet:
raise HTTPException(status_code=404, detail="Wallet not found")
# 소유권 확인
if wallet.user_id != current_user.id:
raise HTTPException(status_code=403, detail="Not your wallet")
# 업데이트 로직
for key, value in wallet_data.dict(exclude_unset=True).items():
setattr(wallet, key, value)
db.commit()
db.refresh(wallet)
return wallet
@router.post("/admin/settings", response_model=SettingsResponse)
async def update_settings(
settings_data: SettingsUpdate,
admin_user: User = Depends(has_permission("admin"))
):
"""관리자 설정 업데이트 (관리자만 가능)"""
# 비즈니스 로직
return update_settings_service(settings_data)
- security 모듈에 필요한 암호화 기능 추가
- auth 모듈에 OAuth2 클라이언트 구현
- dependencies 모듈에 새로운 의존성 함수 추가
결론 ** 모듈화된 인증 시스템은 코드의 가독성, 유지보수성, 확장성을 크게 향상시킵니다.
각 모듈이 명확한 책임을 갖고, 의존성의 방향이 한쪽으로만 흐르도록 설계함으로써, 변경의 영향 범위를 최소화할 수 있습니다.
이 3계층 아키텍처는 작은 프로젝트부터 대규모 마이크로서비스 환경까지 모두 적용할 수 있는 확장성 있는 패턴입니다.
FastAPI 프로젝트에서 인증 시스템을 설계할 때 이 접근법을 고려해 보세요!
다음 글에서는 이 인증 시스템에 블록체인 지갑 인증을 통합하는 방법에 대해 알아보겠습니다. (여러분의 반응이 있다면요)
궁금한 점이 있으면 댓글로 남겨주세요!
