""" 인증 및 권한 미들웨어 JWT 토큰 기반 인증과 역할 기반 접근 제어(RBAC) 구현 """ from fastapi import Depends, HTTPException, status, Request from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from sqlalchemy.orm import Session from typing import List, Optional, Callable, Any from functools import wraps import inspect from ..database import get_db from .jwt_service import jwt_service from .models import UserRepository from ..utils.logger import get_logger logger = get_logger(__name__) security = HTTPBearer() class AuthMiddleware: """인증 미들웨어 클래스""" def __init__(self): self.security = HTTPBearer() async def get_current_user( self, credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db) ) -> dict: """ 현재 사용자 정보 조회 Args: credentials: JWT 토큰 db: 데이터베이스 세션 Returns: dict: 사용자 정보 Raises: HTTPException: 인증 실패 시 """ try: # 토큰 검증 payload = jwt_service.verify_access_token(credentials.credentials) user_id = payload['user_id'] # 사용자 정보 조회 user_repo = UserRepository(db) user = user_repo.find_by_id(user_id) if not user: logger.warning(f"User not found for token: user_id {user_id}") raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="사용자를 찾을 수 없습니다" ) if not user.is_active: logger.warning(f"Inactive user attempted access: {user.username}") raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="비활성화된 계정입니다" ) if user.is_locked(): logger.warning(f"Locked user attempted access: {user.username}") raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="잠긴 계정입니다" ) # 사용자 정보와 토큰 페이로드 결합 user_info = user.to_dict() user_info.update({ 'token_user_id': payload['user_id'], 'token_username': payload['username'], 'token_role': payload['role'] }) return user_info except HTTPException: raise except Exception as e: logger.error(f"Get current user error: {str(e)}") raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="인증 처리 중 오류가 발생했습니다" ) async def get_current_active_user( self, current_user: dict = Depends(get_current_user) ) -> dict: """ 현재 활성 사용자 정보 조회 (별칭) Args: current_user: 현재 사용자 정보 Returns: dict: 사용자 정보 """ return current_user def require_roles(self, allowed_roles: List[str]): """ 특정 역할을 요구하는 의존성 함수 생성 Args: allowed_roles: 허용된 역할 목록 Returns: Callable: 의존성 함수 """ async def role_checker( current_user: dict = Depends(self.get_current_user) ) -> dict: user_role = current_user.get('role') if user_role not in allowed_roles: logger.warning( f"Access denied for user {current_user.get('username')} " f"with role {user_role}. Required roles: {allowed_roles}" ) raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=f"이 기능을 사용하려면 다음 권한이 필요합니다: {', '.join(allowed_roles)}" ) return current_user return role_checker def require_permissions(self, required_permissions: List[str]): """ 특정 권한을 요구하는 의존성 함수 생성 Args: required_permissions: 필요한 권한 목록 Returns: Callable: 의존성 함수 """ async def permission_checker( current_user: dict = Depends(self.get_current_user), db: Session = Depends(get_db) ) -> dict: user_role = current_user.get('role') # 사용자 권한 조회 user_repo = UserRepository(db) user_permissions = user_repo.get_user_permissions(user_role) # 필요한 권한 확인 missing_permissions = [] for permission in required_permissions: if permission not in user_permissions: missing_permissions.append(permission) if missing_permissions: logger.warning( f"Permission denied for user {current_user.get('username')} " f"with role {user_role}. Missing permissions: {missing_permissions}" ) raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=f"이 기능을 사용하려면 다음 권한이 필요합니다: {', '.join(missing_permissions)}" ) # 사용자 정보에 권한 정보 추가 current_user['permissions'] = user_permissions return current_user return permission_checker def require_admin(self): """관리자 권한을 요구하는 의존성 함수""" return self.require_roles(['admin', 'system']) def require_leader_or_admin(self): """팀장 이상 권한을 요구하는 의존성 함수""" return self.require_roles(['admin', 'system', 'leader']) # 전역 인증 미들웨어 인스턴스 auth_middleware = AuthMiddleware() # 편의를 위한 의존성 함수들 get_current_user = auth_middleware.get_current_user get_current_active_user = auth_middleware.get_current_active_user require_admin = auth_middleware.require_admin require_leader_or_admin = auth_middleware.require_leader_or_admin def require_roles(allowed_roles: List[str]): """역할 기반 접근 제어 데코레이터""" return auth_middleware.require_roles(allowed_roles) def require_permissions(required_permissions: List[str]): """권한 기반 접근 제어 데코레이터""" return auth_middleware.require_permissions(required_permissions) # 추가 유틸리티 함수들 async def get_user_from_token(token: str, db: Session) -> Optional[dict]: """ 토큰에서 사용자 정보 추출 (미들웨어 없이 직접 사용) Args: token: JWT 토큰 db: 데이터베이스 세션 Returns: Optional[dict]: 사용자 정보 또는 None """ try: payload = jwt_service.verify_access_token(token) user_id = payload['user_id'] user_repo = UserRepository(db) user = user_repo.find_by_id(user_id) if user and user.is_active and not user.is_locked(): return user.to_dict() return None except Exception as e: logger.error(f"Get user from token error: {str(e)}") return None def check_user_permission(user_role: str, required_permission: str, db: Session) -> bool: """ 사용자 권한 확인 Args: user_role: 사용자 역할 required_permission: 필요한 권한 db: 데이터베이스 세션 Returns: bool: 권한 보유 여부 """ try: user_repo = UserRepository(db) user_permissions = user_repo.get_user_permissions(user_role) return required_permission in user_permissions except Exception as e: logger.error(f"Check user permission error: {str(e)}") return False def get_user_permissions_by_role(role: str, db: Session) -> List[str]: """ 역할별 권한 목록 조회 Args: role: 사용자 역할 db: 데이터베이스 세션 Returns: List[str]: 권한 목록 """ try: user_repo = UserRepository(db) return user_repo.get_user_permissions(role) except Exception as e: logger.error(f"Get user permissions by role error: {str(e)}") return [] # 선택적 인증 (토큰이 있으면 검증, 없으면 None 반환) async def get_current_user_optional( request: Request, db: Session = Depends(get_db) ) -> Optional[dict]: """ 선택적 사용자 인증 (토큰이 있으면 검증, 없으면 None) Args: request: FastAPI Request 객체 db: 데이터베이스 세션 Returns: Optional[dict]: 사용자 정보 또는 None """ try: # Authorization 헤더 확인 authorization = request.headers.get('authorization') if not authorization or not authorization.startswith('Bearer '): return None token = authorization.split(' ')[1] return await get_user_from_token(token, db) except Exception as e: logger.debug(f"Optional auth failed: {str(e)}") return None