유튜브 인증 로직 수정
parent
ec3e0159e8
commit
f47dd423c5
|
|
@ -62,6 +62,7 @@ def _build_redirect_url(is_success: bool, params: dict) -> str:
|
||||||
async def start_connect(
|
async def start_connect(
|
||||||
platform: SocialPlatform,
|
platform: SocialPlatform,
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: User = Depends(get_current_user),
|
||||||
|
session: AsyncSession = Depends(get_session),
|
||||||
) -> SocialConnectResponse:
|
) -> SocialConnectResponse:
|
||||||
"""
|
"""
|
||||||
소셜 계정 연동 시작
|
소셜 계정 연동 시작
|
||||||
|
|
@ -77,6 +78,7 @@ async def start_connect(
|
||||||
return await social_account_service.start_connect(
|
return await social_account_service.start_connect(
|
||||||
user_uuid=current_user.user_uuid,
|
user_uuid=current_user.user_uuid,
|
||||||
platform=platform,
|
platform=platform,
|
||||||
|
session=session,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,13 @@ class BaseOAuthClient(ABC):
|
||||||
platform: SocialPlatform
|
platform: SocialPlatform
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_authorization_url(self, state: str) -> str:
|
def get_authorization_url(self, state: str, force_consent: bool = False) -> str:
|
||||||
"""
|
"""
|
||||||
OAuth 인증 URL 생성
|
OAuth 인증 URL 생성
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
state: CSRF 방지용 state 토큰
|
state: CSRF 방지용 state 토큰
|
||||||
|
force_consent: True면 동의 화면 강제 표시 (refresh_token 재발급 필요 시)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: OAuth 인증 페이지 URL
|
str: OAuth 인증 페이지 URL
|
||||||
|
|
|
||||||
|
|
@ -43,12 +43,13 @@ class YouTubeOAuthClient(BaseOAuthClient):
|
||||||
self.client_secret = social_oauth_settings.YOUTUBE_CLIENT_SECRET
|
self.client_secret = social_oauth_settings.YOUTUBE_CLIENT_SECRET
|
||||||
self.redirect_uri = social_oauth_settings.YOUTUBE_REDIRECT_URI
|
self.redirect_uri = social_oauth_settings.YOUTUBE_REDIRECT_URI
|
||||||
|
|
||||||
def get_authorization_url(self, state: str) -> str:
|
def get_authorization_url(self, state: str, force_consent: bool = False) -> str:
|
||||||
"""
|
"""
|
||||||
Google OAuth 인증 URL 생성
|
Google OAuth 인증 URL 생성
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
state: CSRF 방지용 state 토큰
|
state: CSRF 방지용 state 토큰
|
||||||
|
force_consent: True면 동의 화면 강제 표시하여 refresh_token 재발급
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: Google OAuth 인증 페이지 URL
|
str: Google OAuth 인증 페이지 URL
|
||||||
|
|
@ -58,12 +59,12 @@ class YouTubeOAuthClient(BaseOAuthClient):
|
||||||
"redirect_uri": self.redirect_uri,
|
"redirect_uri": self.redirect_uri,
|
||||||
"response_type": "code",
|
"response_type": "code",
|
||||||
"scope": " ".join(YOUTUBE_SCOPES),
|
"scope": " ".join(YOUTUBE_SCOPES),
|
||||||
"access_type": "offline", # refresh_token 받기 위해 필요
|
"access_type": "offline",
|
||||||
"prompt": "select_account", # 계정 선택만 표시 (동의 화면은 최초 1회만)
|
"prompt": "consent" if force_consent else "select_account",
|
||||||
"state": state,
|
"state": state,
|
||||||
}
|
}
|
||||||
url = f"{self.AUTHORIZATION_URL}?{urlencode(params)}"
|
url = f"{self.AUTHORIZATION_URL}?{urlencode(params)}"
|
||||||
logger.debug(f"[YOUTUBE_OAUTH] 인증 URL 생성: {url[:100]}...")
|
logger.debug(f"[YOUTUBE_OAUTH] 인증 URL 생성 - force_consent: {force_consent}, url: {url[:100]}...")
|
||||||
return url
|
return url
|
||||||
|
|
||||||
async def exchange_code(self, code: str) -> OAuthTokenResponse:
|
async def exchange_code(self, code: str) -> OAuthTokenResponse:
|
||||||
|
|
|
||||||
|
|
@ -59,15 +59,18 @@ class SocialAccountService:
|
||||||
self,
|
self,
|
||||||
user_uuid: str,
|
user_uuid: str,
|
||||||
platform: SocialPlatform,
|
platform: SocialPlatform,
|
||||||
|
session: AsyncSession,
|
||||||
) -> SocialConnectResponse:
|
) -> SocialConnectResponse:
|
||||||
"""
|
"""
|
||||||
소셜 계정 연동 시작
|
소셜 계정 연동 시작
|
||||||
|
|
||||||
OAuth 인증 URL을 생성하고 state 토큰을 저장합니다.
|
OAuth 인증 URL을 생성하고 state 토큰을 저장합니다.
|
||||||
|
기존 연동 계정에 refresh_token이 없으면 동의 화면을 강제 표시합니다.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
user_uuid: 사용자 UUID
|
user_uuid: 사용자 UUID
|
||||||
platform: 연동할 플랫폼
|
platform: 연동할 플랫폼
|
||||||
|
session: DB 세션
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
SocialConnectResponse: OAuth 인증 URL 및 state 토큰
|
SocialConnectResponse: OAuth 인증 URL 및 state 토큰
|
||||||
|
|
@ -77,10 +80,19 @@ class SocialAccountService:
|
||||||
f"user_uuid: {user_uuid}, platform: {platform.value}"
|
f"user_uuid: {user_uuid}, platform: {platform.value}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 1. state 토큰 생성 (CSRF 방지)
|
# 1. 기존 계정의 refresh_token 존재 여부 확인
|
||||||
|
existing_account = await self.get_account_by_platform(user_uuid, platform, session)
|
||||||
|
force_consent = not (existing_account and existing_account.refresh_token)
|
||||||
|
logger.debug(
|
||||||
|
f"[SOCIAL] OAuth prompt 결정 - force_consent: {force_consent}, "
|
||||||
|
f"has_account: {existing_account is not None}, "
|
||||||
|
f"has_refresh_token: {bool(existing_account and existing_account.refresh_token)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2. state 토큰 생성 (CSRF 방지)
|
||||||
state = secrets.token_urlsafe(32)
|
state = secrets.token_urlsafe(32)
|
||||||
|
|
||||||
# 2. state를 Redis에 저장 (user_uuid 포함)
|
# 3. state를 Redis에 저장 (user_uuid 포함)
|
||||||
state_key = f"{self.STATE_KEY_PREFIX}{state}"
|
state_key = f"{self.STATE_KEY_PREFIX}{state}"
|
||||||
state_data = {
|
state_data = {
|
||||||
"user_uuid": user_uuid,
|
"user_uuid": user_uuid,
|
||||||
|
|
@ -89,13 +101,13 @@ class SocialAccountService:
|
||||||
await redis_client.setex(
|
await redis_client.setex(
|
||||||
state_key,
|
state_key,
|
||||||
social_oauth_settings.OAUTH_STATE_TTL_SECONDS,
|
social_oauth_settings.OAUTH_STATE_TTL_SECONDS,
|
||||||
json.dumps(state_data), # JSON으로 직렬화
|
json.dumps(state_data),
|
||||||
)
|
)
|
||||||
logger.debug(f"[SOCIAL] OAuth state 저장 - key: {state_key}")
|
logger.debug(f"[SOCIAL] OAuth state 저장 - key: {state_key}")
|
||||||
|
|
||||||
# 3. OAuth 클라이언트에서 인증 URL 생성
|
# 4. OAuth 클라이언트에서 인증 URL 생성
|
||||||
oauth_client = get_oauth_client(platform)
|
oauth_client = get_oauth_client(platform)
|
||||||
auth_url = oauth_client.get_authorization_url(state)
|
auth_url = oauth_client.get_authorization_url(state, force_consent=force_consent)
|
||||||
|
|
||||||
logger.info(f"[SOCIAL] OAuth URL 생성 완료 - platform: {platform.value}")
|
logger.info(f"[SOCIAL] OAuth URL 생성 완료 - platform: {platform.value}")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue