From 2c8c16c2880a8abb4bb94c437442e783b3f8a9a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=B1=EA=B2=BD?= Date: Mon, 11 May 2026 14:01:26 +0900 Subject: [PATCH] =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/database/session.py | 12 ++++++++---- app/social/services/account_service.py | 2 +- app/user/dependencies/auth.py | 6 +++++- app/user/services/auth.py | 4 ++++ app/user/services/jwt.py | 22 ++++++++++++++++++++++ 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/app/database/session.py b/app/database/session.py index ff0e8e3..6b914b3 100644 --- a/app/database/session.py +++ b/app/database/session.py @@ -1,12 +1,13 @@ import time +import traceback from typing import AsyncGenerator +from fastapi import HTTPException from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from sqlalchemy.orm import DeclarativeBase from app.utils.logger import get_logger from config import db_settings -import traceback logger = get_logger("database") @@ -134,15 +135,16 @@ async def get_session() -> AsyncGenerator[AsyncSession, None]: # ) try: yield session + except HTTPException: + raise except Exception as e: - import traceback await session.rollback() logger.error(traceback.format_exc()) logger.error( f"[get_session] ROLLBACK - error: {type(e).__name__}: {e}, " f"duration: {(time.perf_counter() - start_time)*1000:.1f}ms" ) - raise e + raise finally: total_time = time.perf_counter() - start_time # logger.debug( @@ -170,6 +172,8 @@ async def get_background_session() -> AsyncGenerator[AsyncSession, None]: # ) try: yield session + except HTTPException: + raise except Exception as e: await session.rollback() logger.error( @@ -178,7 +182,7 @@ async def get_background_session() -> AsyncGenerator[AsyncSession, None]: f"duration: {(time.perf_counter() - start_time)*1000:.1f}ms" ) logger.debug(traceback.format_exc()) - raise e + raise finally: total_time = time.perf_counter() - start_time # logger.debug( diff --git a/app/social/services/account_service.py b/app/social/services/account_service.py index 9f4c1aa..0947ad0 100644 --- a/app/social/services/account_service.py +++ b/app/social/services/account_service.py @@ -306,7 +306,7 @@ class SocialAccountService: else: # DB datetime은 naive, now()는 aware이므로 naive로 통일하여 비교 current_time = now().replace(tzinfo=None) - buffer_time = current_time + timedelta(hours=1) + buffer_time = current_time + timedelta(minutes=20) if account.token_expires_at <= buffer_time: should_refresh = True diff --git a/app/user/dependencies/auth.py b/app/user/dependencies/auth.py index 8290f60..9286d81 100644 --- a/app/user/dependencies/auth.py +++ b/app/user/dependencies/auth.py @@ -18,10 +18,11 @@ from app.user.services.auth import ( AdminRequiredError, InvalidTokenError, MissingTokenError, + TokenExpiredError, UserInactiveError, UserNotFoundError, ) -from app.user.services.jwt import decode_token +from app.user.services.jwt import decode_token, is_token_expired logger = logging.getLogger(__name__) @@ -58,6 +59,9 @@ async def get_current_user( payload = decode_token(token) if payload is None: + if is_token_expired(token): + logger.info(f"[AUTH-DEP] Access Token 만료 - token: ...{token[-20:]}") + raise TokenExpiredError() logger.warning(f"[AUTH-DEP] Access Token 디코딩 실패 - token: ...{token[-20:]}") raise InvalidTokenError() diff --git a/app/user/services/auth.py b/app/user/services/auth.py index 31a071a..5cd4445 100644 --- a/app/user/services/auth.py +++ b/app/user/services/auth.py @@ -92,6 +92,7 @@ from app.user.services.jwt import ( get_access_token_expire_seconds, get_refresh_token_expires_at, get_token_hash, + is_token_expired, ) from app.user.services.kakao import kakao_client @@ -212,6 +213,9 @@ class AuthService: # 1. 토큰 디코딩 및 검증 payload = decode_token(refresh_token) if payload is None: + if is_token_expired(refresh_token): + logger.info(f"[AUTH] 토큰 갱신 실패 [1/8 만료] - token: ...{refresh_token[-20:]}") + raise TokenExpiredError() logger.warning(f"[AUTH] 토큰 갱신 실패 [1/8 디코딩] - token: ...{refresh_token[-20:]}") raise InvalidTokenError() diff --git a/app/user/services/jwt.py b/app/user/services/jwt.py index 39f0b5a..6f7ad02 100644 --- a/app/user/services/jwt.py +++ b/app/user/services/jwt.py @@ -116,6 +116,28 @@ def decode_token(token: str) -> Optional[dict]: return None +def is_token_expired(token: str) -> bool: + """ + 토큰이 만료됐는지 확인 (서명/형식은 유효하지만 exp 초과인 경우) + + Returns: + True: 서명은 유효하나 만료된 토큰, False: 형식/서명 자체가 잘못된 토큰 + """ + try: + payload = jwt.decode( + token, + jwt_settings.JWT_SECRET, + algorithms=[jwt_settings.JWT_ALGORITHM], + options={"verify_exp": False}, + ) + exp = payload.get("exp") + if exp is None: + return False + return datetime.fromtimestamp(exp) < datetime.now() + except JWTError: + return False + + def get_token_hash(token: str) -> str: """ 토큰의 SHA-256 해시값 생성