-
🔒
-
관리자 권한이 필요합니다.
-
router.push("/dashboard")}
- className="rounded bg-gray-500 px-4 py-2 text-white hover:bg-gray-600"
- >
- 메인으로 돌아가기
-
+
+
+
관리자 권한 없음
+ {redirectCountdown !== null && (
+
+ 리다이렉트 카운트다운: {redirectCountdown}초 후 {redirectTo}로 이동
+
+ )}
+
{JSON.stringify(authDebugInfo, null, 2)}
+ {fallback ||
관리자 권한이 필요합니다.
}
);
}
- // 모든 조건을 만족하는 경우 자식 컴포넌트 렌더링
+ console.log("AuthGuard: 인증 성공 - 자식 컴포넌트 렌더링");
return <>{children}>;
}
diff --git a/frontend/components/layout/AdminButton.tsx b/frontend/components/layout/AdminButton.tsx
index c88009d7..f5f2bd3f 100644
--- a/frontend/components/layout/AdminButton.tsx
+++ b/frontend/components/layout/AdminButton.tsx
@@ -13,16 +13,40 @@ export function AdminButton({ user }: AdminButtonProps) {
console.log("user:", user);
console.log("user?.userType:", user?.userType);
console.log("user?.isAdmin:", user?.isAdmin);
+ console.log("user?.userId:", user?.userId);
+
+ // 관리자 권한 확인 로직 개선
+ const isAdmin = user?.isAdmin || user?.userType === "ADMIN" || user?.userId === "plm_admin";
+
+ console.log("최종 관리자 권한 확인:", isAdmin);
// 관리자 권한이 있는 사용자만 Admin 버튼 표시
- if (!user || (!user.isAdmin && user.userType !== "admin")) {
+ if (!user || !isAdmin) {
+ console.log("관리자 권한 없음 - Admin 버튼 숨김");
return null;
}
const handleAdminClick = () => {
- // 새 탭으로 관리자 페이지 열기 (토큰 공유를 위해)
+ console.log("Admin 버튼 클릭 - 새 탭으로 어드민 페이지 열기");
+
+ // 토큰 확인
+ const token = localStorage.getItem("authToken");
+ if (!token) {
+ console.log("토큰이 없음 - 로그인 페이지로 이동");
+ window.open(`${window.location.origin}/login`, "_blank");
+ return;
+ }
+
+ console.log("토큰 존재 - 어드민 페이지 열기");
+ // 새 탭으로 관리자 페이지 열기 (localStorage 공유 활용)
const adminUrl = `${window.location.origin}/admin`;
- window.open(adminUrl, "_blank");
+ const newWindow = window.open(adminUrl, "_blank");
+
+ // 새 창이 차단되었는지 확인
+ if (!newWindow) {
+ console.log("팝업이 차단됨 - 같은 창에서 열기");
+ window.location.href = adminUrl;
+ }
};
return (
diff --git a/frontend/hooks/useAuth.ts b/frontend/hooks/useAuth.ts
index 9cadad85..b7f728cd 100644
--- a/frontend/hooks/useAuth.ts
+++ b/frontend/hooks/useAuth.ts
@@ -171,39 +171,106 @@ export const useAuth = () => {
if (!token) {
setUser(null);
setAuthStatus({ isLoggedIn: false, isAdmin: false });
- router.push("/login");
+ console.log("토큰이 없음 - 3초 후 로그인 페이지로 리다이렉트");
+ setTimeout(() => {
+ router.push("/login");
+ }, 3000);
return;
}
- // 병렬로 사용자 정보와 인증 상태 조회
- const [userInfo, authStatusData] = await Promise.all([fetchCurrentUser(), checkAuthStatus()]);
-
console.log("=== refreshUserData 디버깅 ===");
- console.log("userInfo:", userInfo);
- console.log("authStatusData:", authStatusData);
- console.log("authStatusData.isLoggedIn:", authStatusData?.isLoggedIn);
+ console.log("토큰 존재:", !!token);
- setUser(userInfo);
- setAuthStatus(authStatusData);
+ // 토큰이 있으면 임시로 인증된 상태로 설정
+ setAuthStatus({
+ isLoggedIn: true,
+ isAdmin: false, // API 호출 후 업데이트될 예정
+ });
- // 디버깅용 로그
- if (userInfo) {
- console.log("사용자 정보 업데이트:", {
- userId: userInfo.userId,
- userName: userInfo.userName,
- hasPhoto: !!userInfo.photo,
- photoStart: userInfo.photo ? userInfo.photo.substring(0, 50) + "..." : "null",
- });
- }
+ try {
+ // 병렬로 사용자 정보와 인증 상태 조회
+ const [userInfo, authStatusData] = await Promise.all([fetchCurrentUser(), checkAuthStatus()]);
- // 로그인되지 않은 상태인 경우 토큰 제거 (리다이렉트는 useEffect에서 처리)
- if (!authStatusData.isLoggedIn) {
- console.log("로그인되지 않은 상태 - 사용자 정보 제거");
- TokenManager.removeToken();
- setUser(null);
- setAuthStatus({ isLoggedIn: false, isAdmin: false });
- } else {
- console.log("로그인된 상태 - 사용자 정보 유지");
+ console.log("userInfo:", userInfo);
+ console.log("authStatusData:", authStatusData);
+ console.log("authStatusData.isLoggedIn:", authStatusData?.isLoggedIn);
+
+ setUser(userInfo);
+
+ // 관리자 권한 확인 로직 개선
+ let finalAuthStatus = authStatusData;
+ if (userInfo) {
+ // 사용자 정보를 기반으로 관리자 권한 추가 확인
+ const isAdminFromUser = userInfo.userId === "plm_admin" || userInfo.userType === "ADMIN";
+ finalAuthStatus = {
+ isLoggedIn: authStatusData.isLoggedIn,
+ isAdmin: authStatusData.isAdmin || isAdminFromUser,
+ };
+ console.log("관리자 권한 확인:", {
+ userId: userInfo.userId,
+ userType: userInfo.userType,
+ isAdminFromAuth: authStatusData.isAdmin,
+ isAdminFromUser: isAdminFromUser,
+ finalIsAdmin: finalAuthStatus.isAdmin,
+ });
+ }
+
+ setAuthStatus(finalAuthStatus);
+
+ // 디버깅용 로그
+ if (userInfo) {
+ console.log("사용자 정보 업데이트:", {
+ userId: userInfo.userId,
+ userName: userInfo.userName,
+ hasPhoto: !!userInfo.photo,
+ photoStart: userInfo.photo ? userInfo.photo.substring(0, 50) + "..." : "null",
+ });
+ }
+
+ // 로그인되지 않은 상태인 경우 토큰 제거 (리다이렉트는 useEffect에서 처리)
+ if (!finalAuthStatus.isLoggedIn) {
+ console.log("로그인되지 않은 상태 - 사용자 정보 제거");
+ TokenManager.removeToken();
+ setUser(null);
+ setAuthStatus({ isLoggedIn: false, isAdmin: false });
+ } else {
+ console.log("로그인된 상태 - 사용자 정보 유지");
+ }
+ } catch (apiError) {
+ console.error("API 호출 실패:", apiError);
+
+ // API 호출 실패 시에도 토큰이 있으면 임시로 인증된 상태로 처리
+ console.log("API 호출 실패했지만 토큰이 존재하므로 임시로 인증된 상태로 처리");
+
+ // 토큰에서 사용자 정보 추출 시도
+ try {
+ const payload = JSON.parse(atob(token.split(".")[1]));
+ console.log("토큰 페이로드:", payload);
+
+ const tempUser = {
+ userId: payload.userId || "unknown",
+ userName: payload.userName || "사용자",
+ isAdmin: payload.userId === "plm_admin" || payload.userType === "ADMIN",
+ };
+
+ setUser(tempUser);
+ setAuthStatus({
+ isLoggedIn: true,
+ isAdmin: tempUser.isAdmin,
+ });
+
+ console.log("임시 사용자 정보 설정:", tempUser);
+ } catch (tokenError) {
+ console.error("토큰 파싱 실패:", tokenError);
+ // 토큰 파싱도 실패하면 로그인 페이지로 리다이렉트
+ TokenManager.removeToken();
+ setUser(null);
+ setAuthStatus({ isLoggedIn: false, isAdmin: false });
+ console.log("토큰 파싱 실패 - 3초 후 로그인 페이지로 리다이렉트");
+ setTimeout(() => {
+ router.push("/login");
+ }, 3000);
+ }
}
} catch (error) {
console.error("사용자 데이터 새로고침 실패:", error);
@@ -213,7 +280,10 @@ export const useAuth = () => {
TokenManager.removeToken();
setUser(null);
setAuthStatus({ isLoggedIn: false, isAdmin: false });
- router.push("/login");
+ console.log("사용자 데이터 새로고침 실패 - 3초 후 로그인 페이지로 리다이렉트");
+ setTimeout(() => {
+ router.push("/login");
+ }, 3000);
} finally {
setLoading(false);
}
@@ -330,20 +400,43 @@ export const useAuth = () => {
* 초기 인증 상태 확인
*/
useEffect(() => {
+ console.log("=== useAuth 초기 인증 상태 확인 ===");
+ console.log("현재 경로:", window.location.pathname);
+
// 로그인 페이지에서는 인증 상태 확인하지 않음
if (window.location.pathname === "/login") {
+ console.log("로그인 페이지 - 인증 상태 확인 건너뜀");
return;
}
// 토큰이 있는 경우에만 인증 상태 확인
const token = TokenManager.getToken();
+ console.log("localStorage 토큰:", token ? "존재" : "없음");
+
if (token && !TokenManager.isTokenExpired(token)) {
+ console.log("유효한 토큰 존재 - 사용자 데이터 새로고침");
+
+ // 토큰이 있으면 임시로 인증된 상태로 설정 (API 호출 전에)
+ setAuthStatus({
+ isLoggedIn: true,
+ isAdmin: false, // API 호출 후 업데이트될 예정
+ });
+
refreshUserData();
} else if (!token) {
- // 토큰이 없으면 로그인 페이지로 리다이렉트
- router.push("/login");
+ console.log("토큰이 없음 - 3초 후 로그인 페이지로 리다이렉트");
+ // 토큰이 없으면 3초 후 로그인 페이지로 리다이렉트
+ setTimeout(() => {
+ router.push("/login");
+ }, 3000);
+ } else {
+ console.log("토큰 만료 - 3초 후 로그인 페이지로 리다이렉트");
+ TokenManager.removeToken();
+ setTimeout(() => {
+ router.push("/login");
+ }, 3000);
}
- }, []); // refreshUserData 의존성 제거
+ }, [refreshUserData, router]); // refreshUserData 의존성 추가
/**
* 세션 만료 감지 및 처리
diff --git a/frontend/hooks/useUserManagement.ts b/frontend/hooks/useUserManagement.ts
index c6306cc7..ae53607e 100644
--- a/frontend/hooks/useUserManagement.ts
+++ b/frontend/hooks/useUserManagement.ts
@@ -92,9 +92,19 @@ export const useUserManagement = () => {
const response = await userAPI.getList(searchParams);
// 백엔드 응답 구조에 맞게 처리 { success, data, total }
- if (response && response.success && Array.isArray(response.data)) {
- setUsers(response.data);
- setTotalItems(response.total || 0);
+ if (response && response.success && response.data) {
+ // 새로운 API 응답 구조: { success, data: { users, pagination } }
+ if (response.data.users && Array.isArray(response.data.users)) {
+ setUsers(response.data.users);
+ setTotalItems(response.data.pagination?.totalCount || response.data.users.length);
+ } else if (Array.isArray(response.data)) {
+ // 기존 구조: { success, data: User[] }
+ setUsers(response.data);
+ setTotalItems(response.total || response.data.length);
+ } else {
+ setUsers([]);
+ setTotalItems(0);
+ }
} else {
setUsers([]);
setTotalItems(0);
diff --git a/frontend/lib/api/client.ts b/frontend/lib/api/client.ts
index f10161da..d4ef4206 100644
--- a/frontend/lib/api/client.ts
+++ b/frontend/lib/api/client.ts
@@ -37,22 +37,33 @@ apiClient.interceptors.request.use(
(config) => {
// JWT 토큰 추가
const token = TokenManager.getToken();
+ console.log("🔍 API 요청 토큰 확인:", {
+ hasToken: !!token,
+ tokenLength: token ? token.length : 0,
+ tokenStart: token ? token.substring(0, 30) + "..." : "없음",
+ url: config.url,
+ method: config.method,
+ });
+
if (token && !TokenManager.isTokenExpired(token)) {
config.headers.Authorization = `Bearer ${token}`;
- console.log("JWT 토큰 추가됨:", token.substring(0, 50) + "...");
+ console.log("✅ JWT 토큰 추가됨:", token.substring(0, 50) + "...");
+ console.log("🔑 Authorization 헤더:", `Bearer ${token.substring(0, 30)}...`);
} else if (token && TokenManager.isTokenExpired(token)) {
- console.warn("토큰이 만료되었습니다.");
+ console.warn("❌ 토큰이 만료되었습니다.");
// 토큰 제거
if (typeof window !== "undefined") {
localStorage.removeItem("authToken");
}
+ } else {
+ console.warn("⚠️ 토큰이 없습니다.");
}
// 언어 정보를 쿼리 파라미터에 추가
if (config.method?.toUpperCase() === "GET") {
// 전역 언어 상태에서 현재 언어 가져오기
const currentLang = typeof window !== "undefined" ? (window as any).__GLOBAL_USER_LANG || "ko" : "ko";
- console.log("API 요청 시 언어 정보:", currentLang);
+ console.log("🌐 API 요청 시 언어 정보:", currentLang);
if (config.params) {
config.params.userLang = currentLang;
@@ -61,11 +72,12 @@ apiClient.interceptors.request.use(
}
}
- console.log("API 요청:", config.method?.toUpperCase(), config.url, config.params, config.data);
+ console.log("📡 API 요청:", config.method?.toUpperCase(), config.url, config.params, config.data);
+ console.log("📋 요청 헤더:", config.headers);
return config;
},
(error) => {
- console.error("API 요청 오류:", error);
+ console.error("❌ API 요청 오류:", error);
return Promise.reject(error);
},
);
@@ -73,21 +85,34 @@ apiClient.interceptors.request.use(
// 응답 인터셉터
apiClient.interceptors.response.use(
(response: AxiosResponse) => {
- console.log("API 응답:", response.status, response.config.url, response.data);
+ console.log("✅ API 응답:", response.status, response.config.url, response.data);
return response;
},
(error: AxiosError) => {
- console.error("API 응답 오류:", {
+ console.error("❌ API 응답 오류:", {
status: error.response?.status,
statusText: error.response?.statusText,
url: error.config?.url,
data: error.response?.data,
message: error.message,
+ headers: error.config?.headers,
});
+ // 401 에러 시 상세 정보 출력
+ if (error.response?.status === 401) {
+ console.error("🚨 401 Unauthorized 오류 상세 정보:", {
+ url: error.config?.url,
+ method: error.config?.method,
+ headers: error.config?.headers,
+ requestData: error.config?.data,
+ responseData: error.response?.data,
+ token: TokenManager.getToken() ? "존재" : "없음",
+ });
+ }
+
// 401 에러 시 토큰 제거 및 로그인 페이지로 리다이렉트
if (error.response?.status === 401 && typeof window !== "undefined") {
- console.log("401 에러 감지 - 토큰 제거 및 로그인 페이지로 리다이렉트");
+ console.log("🔄 401 에러 감지 - 토큰 제거 및 로그인 페이지로 리다이렉트");
localStorage.removeItem("authToken");
// 로그인 페이지가 아닌 경우에만 리다이렉트
diff --git a/frontend/lib/api/user.ts b/frontend/lib/api/user.ts
index a212d22c..8d98b86f 100644
--- a/frontend/lib/api/user.ts
+++ b/frontend/lib/api/user.ts
@@ -1,4 +1,4 @@
-import { API_BASE_URL } from "./client";
+import { apiClient } from "./client";
/**
* 사용자 관리 API 클라이언트
@@ -15,89 +15,64 @@ interface ApiResponse
{
msg?: string;
}
-/**
- * API 호출 공통 함수
- */
-async function apiCall(endpoint: string, options: RequestInit = {}): Promise> {
- try {
- const response = await fetch(`${API_BASE_URL}${endpoint}`, {
- headers: {
- "Content-Type": "application/json",
- ...options.headers,
- },
- credentials: "include", // 쿠키 포함
- ...options,
- });
-
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
-
- const data = await response.json();
- return data;
- } catch (error) {
- console.error("API 호출 오류:", error);
- throw error;
- }
-}
-
/**
* 사용자 목록 조회
*/
export async function getUserList(params?: Record) {
- const searchParams = new URLSearchParams();
+ try {
+ console.log("📡 사용자 목록 API 호출:", params);
- // 모든 검색 파라미터를 동적으로 처리
- if (params) {
- Object.entries(params).forEach(([key, value]) => {
- if (value !== undefined && value !== null && value !== "") {
- searchParams.append(key, String(value));
- }
+ const response = await apiClient.get("/admin/users", {
+ params: params,
});
+
+ console.log("✅ 사용자 목록 API 응답:", response.data);
+ return response.data;
+ } catch (error) {
+ console.error("❌ 사용자 목록 API 오류:", error);
+ throw error;
}
-
- const queryString = searchParams.toString();
- const endpoint = `/admin/users${queryString ? `?${queryString}` : ""}`;
-
- console.log("📡 최종 API 호출 URL:", endpoint);
- const response = await apiCall(endpoint);
-
- // 전체 response 객체를 그대로 반환 (success, data, total 포함)
- return response;
}
/**
* 사용자 정보 단건 조회
*/
export async function getUserInfo(userId: string) {
- const response = await apiCall(`/admin/users/${userId}`);
+ try {
+ const response = await apiClient.get(`/admin/users/${userId}`);
- if (response.success && response.data) {
- return response.data;
+ if (response.data.success && response.data.data) {
+ return response.data.data;
+ }
+
+ throw new Error(response.data.message || "사용자 정보 조회에 실패했습니다.");
+ } catch (error) {
+ console.error("❌ 사용자 정보 조회 오류:", error);
+ throw error;
}
-
- throw new Error(response.message || "사용자 정보 조회에 실패했습니다.");
}
/**
* 사용자 등록
*/
export async function createUser(userData: any) {
- const response = await apiCall("/admin/users", {
- method: "POST",
- body: JSON.stringify(userData),
- });
+ try {
+ const response = await apiClient.post("/admin/users", userData);
- // 백엔드에서 result 필드를 사용하므로 이에 맞춰 처리
- if (response.result === true || response.success === true) {
- return {
- success: true,
- message: response.msg || response.message || "사용자가 성공적으로 등록되었습니다.",
- data: response,
- };
+ // 백엔드에서 result 필드를 사용하므로 이에 맞춰 처리
+ if (response.data.result === true || response.data.success === true) {
+ return {
+ success: true,
+ message: response.data.msg || response.data.message || "사용자가 성공적으로 등록되었습니다.",
+ data: response.data,
+ };
+ }
+
+ throw new Error(response.data.msg || response.data.message || "사용자 등록에 실패했습니다.");
+ } catch (error) {
+ console.error("❌ 사용자 등록 오류:", error);
+ throw error;
}
-
- throw new Error(response.msg || response.message || "사용자 등록에 실패했습니다.");
}
// 사용자 수정 기능 제거됨
@@ -106,12 +81,9 @@ export async function createUser(userData: any) {
* 사용자 상태 변경
*/
export async function updateUserStatus(userId: string, status: string) {
- const response = await apiCall(`/admin/users/${userId}/status`, {
- method: "PUT",
- body: JSON.stringify({ status }),
- });
+ const response = await apiClient.put(`/admin/users/${userId}/status`, { status });
- return response;
+ return response.data;
}
// 사용자 삭제 기능 제거됨
@@ -137,34 +109,31 @@ export async function getUserHistory(userId: string, params?: Record {
+ const token = localStorage.getItem("authToken");
+ console.log("🔍 토큰 상태 확인:", token ? "존재" : "없음");
+ return !!token;
+ },
+
+ // 토큰 강제 동기화 (다른 탭에서 설정된 토큰을 현재 탭에 복사)
+ forceSync: () => {
+ const token = localStorage.getItem("authToken");
+ if (token) {
+ // sessionStorage에도 복사
+ sessionStorage.setItem("authToken", token);
+ console.log("🔄 토큰 강제 동기화 완료");
+ return true;
+ }
+ return false;
+ },
+
+ // 토큰 복원 시도 (sessionStorage에서 복원)
+ restoreFromSession: () => {
+ const sessionToken = sessionStorage.getItem("authToken");
+ if (sessionToken) {
+ localStorage.setItem("authToken", sessionToken);
+ console.log("🔄 sessionStorage에서 토큰 복원 완료");
+ return true;
+ }
+ return false;
+ },
+
+ // 토큰 유효성 검증
+ validateToken: (token: string) => {
+ if (!token) return false;
+
+ try {
+ // JWT 토큰 구조 확인 (header.payload.signature)
+ const parts = token.split(".");
+ if (parts.length !== 3) return false;
+
+ // payload 디코딩 시도
+ const payload = JSON.parse(atob(parts[1]));
+ const now = Math.floor(Date.now() / 1000);
+
+ // 만료 시간 확인
+ if (payload.exp && payload.exp < now) {
+ console.log("❌ 토큰 만료됨");
+ return false;
+ }
+
+ console.log("✅ 토큰 유효성 검증 통과");
+ return true;
+ } catch (error) {
+ console.log("❌ 토큰 유효성 검증 실패:", error);
+ return false;
+ }
+ },
+};