Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
- H/F/I/O SS304/GRAPHITE/CS/CS 패턴에서 4개 구성요소 모두 표시 - 기존 SS304 + GRAPHITE → SS304/GRAPHITE/CS/CS로 완전한 구성 표시 - 외부링/필러/내부링/추가구성 모든 정보 포함 - 구매수량 계산 모달에서 정확한 재질 정보 확인 가능
306 lines
9.7 KiB
Python
306 lines
9.7 KiB
Python
"""
|
|
인증 및 권한 미들웨어
|
|
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
|