From d3ddfd34a568fcc05882ef89071d561c2e5ab42f Mon Sep 17 00:00:00 2001 From: Dobbymin Date: Fri, 22 Aug 2025 06:15:39 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20OAuth=20=EB=A6=AC=EB=8B=A4=EC=9D=B4?= =?UTF-8?q?=EB=A0=89=ED=8A=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A5=BC=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EB=9D=BC=EC=9A=B0=ED=8A=B8=EC=97=90=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/routes/components/auth.routes.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/app/routes/components/auth.routes.tsx b/src/app/routes/components/auth.routes.tsx index 0817f87..1712695 100644 --- a/src/app/routes/components/auth.routes.tsx +++ b/src/app/routes/components/auth.routes.tsx @@ -1,4 +1,4 @@ -import { LoginPage, RealtorCertificationPage, RealtorLoginPage } from '@/pages'; +import { LoginPage, OAuthRedirectPage, RealtorCertificationPage, RealtorLoginPage } from '@/pages'; import { ROUTER_PATH } from '@/shared'; import { Layout } from '@/widgets'; @@ -14,6 +14,11 @@ export const authRoutes = [ element: , handle: { layout: ROUTE_CONFIG.LOGIN.layout }, }, + { + path: ROUTE_CONFIG.OAUTH_REDIRECT.path, + element: , + handle: { layout: ROUTE_CONFIG.OAUTH_REDIRECT.layout }, + }, ], }, { From e81e8f72b55ef3eee22f645b46732657c205c62d Mon Sep 17 00:00:00 2001 From: Dobbymin Date: Fri, 22 Aug 2025 06:15:48 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat:=20OAuth=20=EB=A6=AC=EB=8B=A4=EC=9D=B4?= =?UTF-8?q?=EB=A0=89=ED=8A=B8=20=EA=B2=BD=EB=A1=9C=EB=A5=BC=20=EB=9D=BC?= =?UTF-8?q?=EC=9A=B0=ED=8A=B8=20=EA=B5=AC=EC=84=B1=EC=97=90=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/routes/config/route-config.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/routes/config/route-config.ts b/src/app/routes/config/route-config.ts index 30c7a56..1c679b3 100644 --- a/src/app/routes/config/route-config.ts +++ b/src/app/routes/config/route-config.ts @@ -31,6 +31,10 @@ export const ROUTE_CONFIG: Record = { path: ROUTER_PATH.REALTOR_CERTIFICATION, layout: 'Auth', }, + OAUTH_REDIRECT: { + path: ROUTER_PATH.OAUTH_REDIRECT, + layout: 'Auth', + }, REALTOR: { path: ROUTER_PATH.REALTOR, requiresRealtor: true, // 공인중개사만 접근 가능 From 95738b0771631a7cf1fec6adae70e4f0742e84e8 Mon Sep 17 00:00:00 2001 From: Dobbymin Date: Fri, 22 Aug 2025 06:16:02 +0900 Subject: [PATCH 3/9] =?UTF-8?q?fix:=20BASE=5FURL=EC=9D=84=20=EB=A1=9C?= =?UTF-8?q?=EC=BB=AC=ED=98=B8=EC=8A=A4=ED=8A=B8=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=8B=A4=EC=A0=9C=20=EC=84=9C=EB=B2=84=20IP=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/libs/fetch-instance.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/shared/libs/fetch-instance.ts b/src/shared/libs/fetch-instance.ts index eed8f57..74c88ae 100644 --- a/src/shared/libs/fetch-instance.ts +++ b/src/shared/libs/fetch-instance.ts @@ -1,10 +1,9 @@ import { initInstance } from './axios-instance'; -export const BASE_URL = 'http://localhost:8080'; +export const BASE_URL = 'http://43.200.101.253'; export const fetchInstance = initInstance({ baseURL: BASE_URL, - withCredentials: true, headers: { 'Content-Type': 'application/json', }, From d59c1c57a8ee1f80bf31005b53093f5b546a7c6e Mon Sep 17 00:00:00 2001 From: Dobbymin Date: Fri, 22 Aug 2025 06:16:11 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=EB=9D=BC=EC=9A=B0=ED=84=B0=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=EC=97=90=20OAUTH=5FREDIRECT=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/constants/router-path.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shared/constants/router-path.ts b/src/shared/constants/router-path.ts index bceeeeb..3b0f1af 100644 --- a/src/shared/constants/router-path.ts +++ b/src/shared/constants/router-path.ts @@ -3,6 +3,7 @@ export const ROUTER_PATH = { MAIN: '/', REALTOR: '/realtor', LOGIN: '/login', + OAUTH_REDIRECT: '/oauth/redirect', RISK_DIAGNOSTICS: '/diagnostics/risk', RELIABILITY_DIAGNOSTICS: '/diagnostics/reliability', REALTOR_LOGIN: '/realtor/login', From c08eb984c303906ca1673e027d0a0d29ca7caf4d Mon Sep 17 00:00:00 2001 From: Dobbymin Date: Fri, 22 Aug 2025 06:16:23 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20OAuth=20=EB=A6=AC=EB=8B=A4=EC=9D=B4?= =?UTF-8?q?=EB=A0=89=ED=8A=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=AA=A8=EB=93=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/index.ts | 1 + src/pages/oauth/OAuthRedirectPage.tsx | 110 ++++++++++++++++++++++++++ src/pages/oauth/index.ts | 1 + 3 files changed, 112 insertions(+) create mode 100644 src/pages/oauth/OAuthRedirectPage.tsx create mode 100644 src/pages/oauth/index.ts diff --git a/src/pages/index.ts b/src/pages/index.ts index 5d0aa3c..ab03dd7 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -2,3 +2,4 @@ export * from './main'; export * from './realtor'; export * from './login'; export * from './diagnostics'; +export * from './oauth'; diff --git a/src/pages/oauth/OAuthRedirectPage.tsx b/src/pages/oauth/OAuthRedirectPage.tsx new file mode 100644 index 0000000..45fcf9d --- /dev/null +++ b/src/pages/oauth/OAuthRedirectPage.tsx @@ -0,0 +1,110 @@ +import { useEffect, useState } from 'react'; +import { useNavigate, useSearchParams } from 'react-router-dom'; + +import { useGetAuthTicket } from '@/entities/auth'; + +import { ROUTER_PATH } from '@/shared'; + +export default function OAuthRedirectPage() { + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + const [error, setError] = useState(null); + + // URL에서 ticket 파라미터 추출 + const ticket = searchParams.get('ticket'); + const oauthError = searchParams.get('error'); + const errorDescription = searchParams.get('error_description'); + + // OAuth 에러 처리 + useEffect(() => { + if (oauthError) { + setError(errorDescription || `OAuth 인증 오류: ${oauthError}`); + } + }, [oauthError, errorDescription]); + + // ticket이 없으면 에러 처리 + useEffect(() => { + if (!oauthError && !ticket) { + setError('인증 토큰이 없습니다. 다시 로그인해주세요.'); + } + }, [ticket, oauthError]); + + // ticket이 있고 OAuth 에러가 없을 때만 API 호출 + const shouldCallApi = !!ticket && !oauthError; + const { + data: authData, + error: apiError, + isLoading, + } = useGetAuthTicket(shouldCallApi ? ticket : ''); + + // API 에러 처리 + useEffect(() => { + if (apiError) { + console.error('API 호출 중 오류:', apiError); + setError('인증 검증에 실패했습니다. 다시 시도해주세요.'); + } + }, [apiError]); + + // 성공 시 처리 + useEffect(() => { + if (authData && !isLoading && !apiError && shouldCallApi) { + console.log('OAuth 인증 성공!', authData); + + // refreshToken을 localStorage에 저장 + localStorage.setItem('refreshToken', authData.refreshToken); + + // 메인 페이지로 이동 + navigate(ROUTER_PATH.MAIN, { replace: true }); + } + }, [authData, isLoading, apiError, navigate, shouldCallApi]); + + if (isLoading && shouldCallApi) { + return ( +
+
+
+
+
+

인증 처리 중...

+

잠시만 기다려주세요.

+
+
+ ); + } + + if (error) { + return ( +
+
+
+
+ + + +
+
+

인증 실패

+

{error}

+ +
+
+ ); + } + + return null; +} diff --git a/src/pages/oauth/index.ts b/src/pages/oauth/index.ts new file mode 100644 index 0000000..2127646 --- /dev/null +++ b/src/pages/oauth/index.ts @@ -0,0 +1 @@ +export { default as OAuthRedirectPage } from './OAuthRedirectPage'; From 0c4335d79ad47ced682b0224da0a3e06a3ff44bd Mon Sep 17 00:00:00 2001 From: Dobbymin Date: Fri, 22 Aug 2025 06:16:36 +0900 Subject: [PATCH 6/9] =?UTF-8?q?feat:=20=EC=9D=B8=EC=A6=9D=20API=20?= =?UTF-8?q?=EB=AA=A8=EB=93=88=20=EB=B0=8F=20get-ticket=20API=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/auth/apis/get-ticket.api.ts | 16 ++++++++++++++++ src/entities/auth/apis/index.ts | 1 + src/entities/auth/index.ts | 2 ++ 3 files changed, 19 insertions(+) create mode 100644 src/entities/auth/apis/get-ticket.api.ts create mode 100644 src/entities/auth/apis/index.ts diff --git a/src/entities/auth/apis/get-ticket.api.ts b/src/entities/auth/apis/get-ticket.api.ts new file mode 100644 index 0000000..5e7d8e7 --- /dev/null +++ b/src/entities/auth/apis/get-ticket.api.ts @@ -0,0 +1,16 @@ +import { fetchInstance } from '@/shared'; + +export const GET_TICKET_API_PATH = '/api/auth/ticket'; + +interface GetTicketApiResponse { + refreshToken: string; +} + +export const getTicketApi = async (ticket: string): Promise => { + const response = await fetchInstance.get(GET_TICKET_API_PATH, { + params: { + ticket, + }, + }); + return response.data; +}; diff --git a/src/entities/auth/apis/index.ts b/src/entities/auth/apis/index.ts new file mode 100644 index 0000000..5cbea31 --- /dev/null +++ b/src/entities/auth/apis/index.ts @@ -0,0 +1 @@ +export * from './get-ticket.api'; diff --git a/src/entities/auth/index.ts b/src/entities/auth/index.ts index e69de29..fa88d83 100644 --- a/src/entities/auth/index.ts +++ b/src/entities/auth/index.ts @@ -0,0 +1,2 @@ +export * from './apis'; +export * from './hooks'; From 674f7801c1d604d9870845ded8cac7278d4724b3 Mon Sep 17 00:00:00 2001 From: Dobbymin Date: Fri, 22 Aug 2025 06:16:49 +0900 Subject: [PATCH 7/9] =?UTF-8?q?feat:=20=EC=9D=B8=EC=A6=9D=20=ED=8B=B0?= =?UTF-8?q?=EC=BC=93=EC=9D=84=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20?= =?UTF-8?q?=ED=9B=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/auth/hooks/getAuthTicket.ts | 14 ++++++++++++++ src/entities/auth/hooks/index.ts | 1 + 2 files changed, 15 insertions(+) create mode 100644 src/entities/auth/hooks/getAuthTicket.ts create mode 100644 src/entities/auth/hooks/index.ts diff --git a/src/entities/auth/hooks/getAuthTicket.ts b/src/entities/auth/hooks/getAuthTicket.ts new file mode 100644 index 0000000..0dbd3f2 --- /dev/null +++ b/src/entities/auth/hooks/getAuthTicket.ts @@ -0,0 +1,14 @@ +import { useQuery } from '@tanstack/react-query'; + +import { getTicketApi } from '../apis'; + +export const GetAuthTicketQueryKey = { + ticket: (ticket: string) => ['ticket', ticket], +}; + +export const useGetAuthTicket = (ticket: string) => { + return useQuery({ + queryKey: GetAuthTicketQueryKey.ticket(ticket), + queryFn: () => getTicketApi(ticket), + }); +}; diff --git a/src/entities/auth/hooks/index.ts b/src/entities/auth/hooks/index.ts new file mode 100644 index 0000000..df60f1b --- /dev/null +++ b/src/entities/auth/hooks/index.ts @@ -0,0 +1 @@ +export * from './getAuthTicket'; From af756a8cc52985e87f525dd6c9a3ecc520d3e576 Mon Sep 17 00:00:00 2001 From: Dobbymin Date: Fri, 22 Aug 2025 06:16:59 +0900 Subject: [PATCH 8/9] =?UTF-8?q?feat:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=B2=84=ED=8A=BC=EC=97=90=20OAu?= =?UTF-8?q?th=20=EB=A6=AC=EB=8B=A4=EC=9D=B4=EB=A0=89=ED=8A=B8=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/common/buttons/KakaoLoginButton.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/features/login/components/common/buttons/KakaoLoginButton.tsx b/src/features/login/components/common/buttons/KakaoLoginButton.tsx index 4dc0621..67aefd7 100644 --- a/src/features/login/components/common/buttons/KakaoLoginButton.tsx +++ b/src/features/login/components/common/buttons/KakaoLoginButton.tsx @@ -1,10 +1,19 @@ -import { Button } from '@/shared'; +import { BASE_URL, Button } from '@/shared'; import KakaoSymbol from '../../../_assets/kakao-symbol.webp'; export const KakaoLoginButton = () => { + const kakaoLogin = () => { + // OAuth 서버로 리다이렉트하되, 리다이렉트 URL을 명시적으로 지정 + const redirectUri = encodeURIComponent(`${window.location.origin}/oauth/redirect`); + window.location.href = `${BASE_URL}/oauth2/authorization/kakao?redirect_uri=${redirectUri}`; + }; + return ( - From 8151831a03e07f30fad23851ecb6153a021bbcb7 Mon Sep 17 00:00:00 2001 From: Dobbymin Date: Fri, 22 Aug 2025 06:35:09 +0900 Subject: [PATCH 9/9] =?UTF-8?q?fix:=20OAuth=20=EB=A6=AC=EB=8B=A4=EC=9D=B4?= =?UTF-8?q?=EB=A0=89=ED=8A=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=9D=98=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/oauth/OAuthRedirectPage.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/pages/oauth/OAuthRedirectPage.tsx b/src/pages/oauth/OAuthRedirectPage.tsx index 45fcf9d..0569a70 100644 --- a/src/pages/oauth/OAuthRedirectPage.tsx +++ b/src/pages/oauth/OAuthRedirectPage.tsx @@ -15,19 +15,13 @@ export default function OAuthRedirectPage() { const oauthError = searchParams.get('error'); const errorDescription = searchParams.get('error_description'); - // OAuth 에러 처리 useEffect(() => { if (oauthError) { setError(errorDescription || `OAuth 인증 오류: ${oauthError}`); - } - }, [oauthError, errorDescription]); - - // ticket이 없으면 에러 처리 - useEffect(() => { - if (!oauthError && !ticket) { + } else if (!ticket) { setError('인증 토큰이 없습니다. 다시 로그인해주세요.'); } - }, [ticket, oauthError]); + }, [oauthError, errorDescription, ticket]); // ticket이 있고 OAuth 에러가 없을 때만 API 호출 const shouldCallApi = !!ticket && !oauthError;