Files
TK-BOM-Project/backend/app/auth/jwt_service.py
Hyungi Ahn 83b90ef05c
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
feat: 자재 관리 페이지 대규모 개선
- 파이프 수량 계산 로직 수정 (단관 개수가 아닌 실제 길이 기반 계산)
- UI 전면 개편 (DevonThink 스타일의 간결하고 세련된 디자인)
- 자재별 그룹핑 로직 개선:
  * 플랜지: 동일 사양별 그룹핑, WN 스케줄 표시, ORIFICE 풀네임 표시
  * 피팅: 상세 타입 표시 (니플 길이, 엘보 각도/연결, 티 타입, 리듀서 타입 등)
  * 밸브: 동일 사양별 그룹핑, 타입/연결방식/압력 표시
  * 볼트: 크기/재질/길이별 그룹핑 (8SET → 개별 집계)
  * 가스켓: 동일 사양별 그룹핑, 재질/상세내역/두께 분리 표시
  * UNKNOWN: 원본 설명 전체 표시, 동일 항목 그룹핑
- 전체 카테고리 버튼 제거 (표시 복잡도 감소)
- 카테고리별 동적 컬럼 헤더 및 레이아웃 적용
2025-09-09 09:24:45 +09:00

268 lines
8.7 KiB
Python

"""
JWT 토큰 관리 서비스
TK-FB-Project의 JWT 구현을 참고하여 FastAPI용으로 구현
"""
import jwt
from datetime import datetime, timedelta
from typing import Optional, Dict, Any
from fastapi import HTTPException, status
import os
from ..config import get_settings
from ..utils.logger import get_logger
settings = get_settings()
logger = get_logger(__name__)
# JWT 설정
JWT_SECRET = os.getenv('JWT_SECRET', 'tkmp-secret-key-2025')
JWT_REFRESH_SECRET = os.getenv('JWT_REFRESH_SECRET', 'tkmp-refresh-secret-2025')
JWT_ALGORITHM = 'HS256'
ACCESS_TOKEN_EXPIRE_HOURS = int(os.getenv('JWT_EXPIRES_IN_HOURS', '24'))
REFRESH_TOKEN_EXPIRE_DAYS = int(os.getenv('JWT_REFRESH_EXPIRES_IN_DAYS', '7'))
class JWTService:
"""JWT 토큰 관리 서비스"""
@staticmethod
def create_access_token(user_data: Dict[str, Any]) -> str:
"""
Access Token 생성
Args:
user_data: 사용자 정보 딕셔너리
Returns:
str: JWT Access Token
"""
try:
# 토큰 만료 시간 설정
expire = datetime.utcnow() + timedelta(hours=ACCESS_TOKEN_EXPIRE_HOURS)
# 토큰 페이로드 구성
payload = {
'user_id': user_data['user_id'],
'username': user_data['username'],
'name': user_data['name'],
'role': user_data['role'],
'access_level': user_data['access_level'],
'exp': expire,
'iat': datetime.utcnow(),
'type': 'access'
}
# JWT 토큰 생성
token = jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)
logger.debug(f"Access token created for user: {user_data['username']}")
return token
except Exception as e:
logger.error(f"Access token creation failed: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="토큰 생성에 실패했습니다"
)
@staticmethod
def create_refresh_token(user_id: int) -> str:
"""
Refresh Token 생성
Args:
user_id: 사용자 ID
Returns:
str: JWT Refresh Token
"""
try:
# 토큰 만료 시간 설정
expire = datetime.utcnow() + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)
# 토큰 페이로드 구성
payload = {
'user_id': user_id,
'exp': expire,
'iat': datetime.utcnow(),
'type': 'refresh'
}
# JWT 토큰 생성
token = jwt.encode(payload, JWT_REFRESH_SECRET, algorithm=JWT_ALGORITHM)
logger.debug(f"Refresh token created for user_id: {user_id}")
return token
except Exception as e:
logger.error(f"Refresh token creation failed: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="리프레시 토큰 생성에 실패했습니다"
)
@staticmethod
def verify_access_token(token: str) -> Dict[str, Any]:
"""
Access Token 검증
Args:
token: JWT Access Token
Returns:
Dict[str, Any]: 토큰 페이로드
Raises:
HTTPException: 토큰 검증 실패 시
"""
try:
# JWT 토큰 디코딩
payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
# 토큰 타입 확인
if payload.get('type') != 'access':
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="잘못된 토큰 타입입니다"
)
# 필수 필드 확인
required_fields = ['user_id', 'username', 'role']
for field in required_fields:
if field not in payload:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"토큰에 {field} 정보가 없습니다"
)
logger.debug(f"Access token verified for user: {payload['username']}")
return payload
except jwt.ExpiredSignatureError:
logger.warning("Access token expired")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="토큰이 만료되었습니다"
)
except jwt.InvalidTokenError as e:
logger.warning(f"Invalid access token: {str(e)}")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="유효하지 않은 토큰입니다"
)
except Exception as e:
logger.error(f"Access token verification failed: {str(e)}")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="토큰 검증에 실패했습니다"
)
@staticmethod
def verify_refresh_token(token: str) -> Dict[str, Any]:
"""
Refresh Token 검증
Args:
token: JWT Refresh Token
Returns:
Dict[str, Any]: 토큰 페이로드
Raises:
HTTPException: 토큰 검증 실패 시
"""
try:
# JWT 토큰 디코딩
payload = jwt.decode(token, JWT_REFRESH_SECRET, algorithms=[JWT_ALGORITHM])
# 토큰 타입 확인
if payload.get('type') != 'refresh':
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="잘못된 리프레시 토큰 타입입니다"
)
# 필수 필드 확인
if 'user_id' not in payload:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="토큰에 사용자 정보가 없습니다"
)
logger.debug(f"Refresh token verified for user_id: {payload['user_id']}")
return payload
except jwt.ExpiredSignatureError:
logger.warning("Refresh token expired")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="리프레시 토큰이 만료되었습니다"
)
except jwt.InvalidTokenError as e:
logger.warning(f"Invalid refresh token: {str(e)}")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="유효하지 않은 리프레시 토큰입니다"
)
except Exception as e:
logger.error(f"Refresh token verification failed: {str(e)}")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="리프레시 토큰 검증에 실패했습니다"
)
@staticmethod
def get_token_expiry_info(token: str, token_type: str = 'access') -> Dict[str, Any]:
"""
토큰 만료 정보 조회
Args:
token: JWT Token
token_type: 토큰 타입 ('access' 또는 'refresh')
Returns:
Dict[str, Any]: 토큰 만료 정보
"""
try:
secret = JWT_SECRET if token_type == 'access' else JWT_REFRESH_SECRET
payload = jwt.decode(token, secret, algorithms=[JWT_ALGORITHM])
exp_timestamp = payload.get('exp')
iat_timestamp = payload.get('iat')
if exp_timestamp:
exp_datetime = datetime.fromtimestamp(exp_timestamp)
remaining_time = exp_datetime - datetime.utcnow()
return {
'expires_at': exp_datetime,
'issued_at': datetime.fromtimestamp(iat_timestamp) if iat_timestamp else None,
'remaining_seconds': int(remaining_time.total_seconds()),
'is_expired': remaining_time.total_seconds() <= 0
}
return {'error': '토큰에 만료 시간 정보가 없습니다'}
except Exception as e:
logger.error(f"Token expiry info retrieval failed: {str(e)}")
return {'error': str(e)}
# JWT 서비스 인스턴스
jwt_service = JWTService()