diff --git a/.env.sample b/.env.sample index 9bbadab2..5734649e 100644 --- a/.env.sample +++ b/.env.sample @@ -110,7 +110,7 @@ HYDRA_PUBLIC_URL=${OATHKEEPER_PUBLIC_URL}/oidc # OIDC 클라이언트 callback (콤마 구분) ADMINFRONT_CALLBACK_URLS=http://localhost:5173/auth/callback,https://sso.hmac.kr/auth/callback -DEVFRONT_CALLBACK_URLS=http://localhost:5174/callback,https://sso.hmac.kr/devfront/callback +DEVFRONT_CALLBACK_URLS=http://localhost:5174/auth/callback,https://sso.hmac.kr/devfront/auth/callback # Kratos allowed_return_urls 확장 목록 (콤마 구분, 선택) # 기본값은 KRATOS_UI_URL, USERFRONT_URL, 각 callback URL을 자동 포함합니다. @@ -134,9 +134,9 @@ CSRF_COOKIE_NAME=__HOST-baronSSO_csrf CSRF_COOKIE_SECRET=localcsrf123 # AdminFront OIDC 설정 -ADMINFRONT_CALLBACK_URLS=http://localhost:5000/callback,https://sso.hmac.kr/devfront/callback +ADMINFRONT_CALLBACK_URLS=http://localhost:5173/auth/callback,https://sso.hmac.kr/auth/callback # DevFront OIDC 설정 VITE_OIDC_CLIENT_ID=devfront VITE_OIDC_AUTHORITY=https://sso.hmac.kr/oidc -DEVFRONT_CALLBACK_URLS=http://localhost:5174/callback,https://sso.hmac.kr/devfront/callback \ No newline at end of file +DEVFRONT_CALLBACK_URLS=http://localhost:5174/auth/callback,https://sso.hmac.kr/devfront/auth/callback \ No newline at end of file diff --git a/adminfront/package-lock.json b/adminfront/package-lock.json index e26a6fad..f206ce9b 100644 --- a/adminfront/package-lock.json +++ b/adminfront/package-lock.json @@ -20,9 +20,11 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.563.0", + "oidc-client-ts": "^3.4.1", "react": "^19.2.0", "react-dom": "^19.2.0", "react-hook-form": "^7.71.1", + "react-oidc-context": "^3.3.0", "react-router-dom": "^6.28.2", "sonner": "^2.0.7", "tailwind-merge": "^3.4.0", @@ -3163,6 +3165,15 @@ "node": ">=6" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/lightningcss": { "version": "1.31.1", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", @@ -3605,6 +3616,18 @@ "node": ">= 6" } }, + "node_modules/oidc-client-ts": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.4.1.tgz", + "integrity": "sha512-jNdst/U28Iasukx/L5MP6b274Vr7ftQs6qAhPBCvz6Wt5rPCA+Q/tUmCzfCHHWweWw5szeMy2Gfrm1rITwUKrw==", + "license": "Apache-2.0", + "dependencies": { + "jwt-decode": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -3926,6 +3949,19 @@ "react": "^16.8.0 || ^17 || ^18 || ^19" } }, + "node_modules/react-oidc-context": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/react-oidc-context/-/react-oidc-context-3.3.0.tgz", + "integrity": "sha512-302T/ma4AOVAxrHdYctDSKXjCq9KNHT564XEO2yOPxRfxEP58xa4nz+GQinNl8x7CnEXECSM5JEjQJk3Cr5BvA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "oidc-client-ts": "^3.1.0", + "react": ">=16.14.0" + } + }, "node_modules/react-refresh": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", diff --git a/adminfront/package.json b/adminfront/package.json index d91f2c03..fa57ca92 100644 --- a/adminfront/package.json +++ b/adminfront/package.json @@ -26,9 +26,11 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.563.0", + "oidc-client-ts": "^3.4.1", "react": "^19.2.0", "react-dom": "^19.2.0", "react-hook-form": "^7.71.1", + "react-oidc-context": "^3.3.0", "react-router-dom": "^6.28.2", "sonner": "^2.0.7", "tailwind-merge": "^3.4.0", diff --git a/adminfront/src/components/common/LanguageSelector.tsx b/adminfront/src/components/common/LanguageSelector.tsx index fe77df72..7f905cd0 100644 --- a/adminfront/src/components/common/LanguageSelector.tsx +++ b/adminfront/src/components/common/LanguageSelector.tsx @@ -44,12 +44,8 @@ function LanguageSelector() { className="rounded-full border border-border bg-transparent px-3 py-2 text-sm text-muted-foreground transition hover:bg-muted/20" aria-label={t("ui.common.language", "언어")} > - - + + ); } diff --git a/adminfront/src/components/layout/AppLayout.tsx b/adminfront/src/components/layout/AppLayout.tsx index 7baec948..6fda7c27 100644 --- a/adminfront/src/components/layout/AppLayout.tsx +++ b/adminfront/src/components/layout/AppLayout.tsx @@ -12,6 +12,7 @@ import { Users, } from "lucide-react"; import { useEffect, useState } from "react"; +import { useAuth } from "react-oidc-context"; import { NavLink, Outlet, useNavigate } from "react-router-dom"; import { t } from "../../lib/i18n"; import LanguageSelector from "../common/LanguageSelector"; @@ -40,6 +41,7 @@ const navItems = [ { label: "ui.admin.nav.auth_guard", to: "/auth", icon: KeyRound }, ]; function AppLayout() { + const auth = useAuth(); const navigate = useNavigate(); const [theme, setTheme] = useState<"light" | "dark">(() => { const stored = window.localStorage.getItem("admin_theme"); @@ -51,16 +53,16 @@ function AppLayout() { window.confirm(t("msg.admin.logout_confirm", "로그아웃 하시겠습니까?")) ) { window.localStorage.removeItem("admin_session"); + auth.removeUser(); navigate("/login"); } }; useEffect(() => { - const session = window.localStorage.getItem("admin_session"); - if (!session) { + if (!auth.isLoading && !auth.isAuthenticated) { navigate("/login"); } - }, [navigate]); + }, [auth.isLoading, auth.isAuthenticated, navigate]); useEffect(() => { const root = document.documentElement; @@ -77,6 +79,14 @@ function AppLayout() { setTheme((prev) => (prev === "light" ? "dark" : "light")); }; + if (auth.isLoading) { + return ( +
+
+
+ ); + } + return (