메뉴관리, 다국어관리, 토큰문제 해결
This commit is contained in:
@@ -837,6 +837,9 @@ export const logger = winston.createLogger({
|
||||
12. **JWT 토큰 관리**: 프론트엔드 API 클라이언트에서 JWT 토큰을 자동으로 포함하여 인증 문제 해결
|
||||
13. **환경변수 관리**: Prisma 스키마에서 직접 데이터베이스 URL 설정으로 환경변수 로딩 문제 해결
|
||||
14. **어드민 메뉴 인증**: 새 탭에서 열리는 어드민 페이지의 토큰 인증 문제 해결 - localStorage 공유 활용
|
||||
15. **관리자 메뉴 내 페이지 이동 토큰 문제**: 레이아웃 레벨 토큰 확인 및 동기화 구현
|
||||
16. **API 클라이언트 통일**: 모든 API에서 apiClient 사용으로 토큰 자동 전달 보장
|
||||
17. **토큰 동기화 유틸리티**: localStorage와 sessionStorage 간 토큰 동기화 및 복원 기능
|
||||
|
||||
## 🔐 인증 및 보안 가이드
|
||||
|
||||
@@ -972,6 +975,175 @@ const TokenManager = {
|
||||
};
|
||||
```
|
||||
|
||||
#### 토큰 동기화 유틸리티
|
||||
|
||||
```typescript
|
||||
// lib/sessionManager.ts
|
||||
export const tokenSync = {
|
||||
// 토큰 상태 확인
|
||||
checkToken: () => {
|
||||
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;
|
||||
}
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
#### 관리자 레이아웃 토큰 확인
|
||||
|
||||
```typescript
|
||||
// app/(main)/admin/layout.tsx
|
||||
export default function AdminLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const [isAuthorized, setIsAuthorized] = useState<boolean | null>(null);
|
||||
|
||||
// 토큰 확인 및 인증 상태 체크
|
||||
useEffect(() => {
|
||||
const checkToken = () => {
|
||||
const token = localStorage.getItem("authToken");
|
||||
|
||||
// 토큰이 없으면 sessionStorage에서 복원 시도
|
||||
if (!token && sessionToken) {
|
||||
const restored = tokenSync.restoreFromSession();
|
||||
if (restored) {
|
||||
setIsAuthorized(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 토큰 유효성 검증
|
||||
if (token && !tokenSync.validateToken(token)) {
|
||||
localStorage.removeItem("authToken");
|
||||
sessionStorage.removeItem("authToken");
|
||||
setIsAuthorized(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
setIsAuthorized(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 토큰이 있으면 인증된 것으로 간주
|
||||
setIsAuthorized(true);
|
||||
|
||||
// 토큰 강제 동기화 (다른 탭과 동기화)
|
||||
tokenSync.forceSync();
|
||||
};
|
||||
|
||||
// 초기 토큰 확인
|
||||
checkToken();
|
||||
|
||||
// localStorage 변경 이벤트 리스너 추가
|
||||
const handleStorageChange = (e: StorageEvent) => {
|
||||
if (e.key === "authToken") {
|
||||
checkToken();
|
||||
}
|
||||
};
|
||||
|
||||
// 페이지 포커스 시 토큰 재확인
|
||||
const handleFocus = () => {
|
||||
checkToken();
|
||||
};
|
||||
|
||||
window.addEventListener("storage", handleStorageChange);
|
||||
window.addEventListener("focus", handleFocus);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("storage", handleStorageChange);
|
||||
window.removeEventListener("focus", handleFocus);
|
||||
};
|
||||
}, [pathname]);
|
||||
}
|
||||
```
|
||||
|
||||
#### API 클라이언트 통일
|
||||
|
||||
```typescript
|
||||
// lib/api/user.ts - 수정 전 (fetch 사용)
|
||||
async function apiCall<T = any>(
|
||||
endpoint: string,
|
||||
options: RequestInit = {}
|
||||
): Promise<ApiResponse<T>> {
|
||||
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
credentials: "include",
|
||||
...options,
|
||||
});
|
||||
// 토큰 수동 추가 필요
|
||||
}
|
||||
|
||||
// lib/api/user.ts - 수정 후 (apiClient 사용)
|
||||
export async function getUserList(params?: Record<string, any>) {
|
||||
try {
|
||||
const response = await apiClient.get("/admin/users", {
|
||||
params: params,
|
||||
});
|
||||
// 토큰 자동 추가됨
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("❌ 사용자 목록 API 오류:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 백엔드 토큰 검증
|
||||
|
||||
```typescript
|
||||
@@ -1011,6 +1183,38 @@ export const authenticateToken = (
|
||||
};
|
||||
```
|
||||
|
||||
### 토큰 인증 문제 해결 완료 사항
|
||||
|
||||
#### ✅ 해결된 문제들
|
||||
|
||||
1. **어드민 메뉴 토큰 인증 문제**
|
||||
|
||||
- 새 탭에서 열리는 어드민 페이지의 토큰 공유 ✅
|
||||
- localStorage 기반 토큰 동기화 ✅
|
||||
|
||||
2. **관리자 메뉴 내 페이지 이동 시 토큰 문제**
|
||||
|
||||
- 레이아웃 레벨에서 토큰 확인 로직 추가 ✅
|
||||
- 실시간 토큰 동기화 및 검증 ✅
|
||||
|
||||
3. **사용자 관리 메뉴 특정 인증 문제**
|
||||
- API 클라이언트 통일 (fetch → apiClient) ✅
|
||||
- 토큰 자동 전달 활성화 ✅
|
||||
|
||||
#### 🔧 구현된 기능들
|
||||
|
||||
- **토큰 동기화 유틸리티**: `tokenSync` 모듈
|
||||
- **강화된 인증 체크**: 레이아웃 레벨 토큰 검증
|
||||
- **API 클라이언트 통일**: 모든 API에서 토큰 자동 전달
|
||||
- **디버깅 도구**: 상세한 토큰 상태 확인 및 API 테스트
|
||||
|
||||
#### 📝 테스트 방법
|
||||
|
||||
1. **Admin 버튼 클릭** → 어드민 페이지 열기
|
||||
2. **사이드바 메뉴 클릭** → 다른 관리자 페이지로 이동
|
||||
3. **디버깅 페이지 확인** → `/admin/debug-layout`에서 토큰 상태 확인
|
||||
4. **API 테스트** → 각 메뉴에서 API 호출 정상 작동 확인
|
||||
|
||||
## 🎯 성공 지표
|
||||
|
||||
1. **성능 개선**: API 응답 시간 30% 단축
|
||||
@@ -1021,6 +1225,6 @@ export const authenticateToken = (
|
||||
---
|
||||
|
||||
**마지막 업데이트**: 2024년 12월 20일
|
||||
**버전**: 1.8.0
|
||||
**버전**: 1.9.0
|
||||
**작성자**: AI Assistant
|
||||
**현재 상태**: Phase 1 완료, Phase 2-1A 완료, Phase 2-1B 완료, Phase 2-2A 완료 ✅ (메뉴 API 구현 완료, 어드민 메뉴 인증 문제 해결)
|
||||
**현재 상태**: Phase 1 완료, Phase 2-1A 완료, Phase 2-1B 완료, Phase 2-2A 완료 ✅ (메뉴 API 구현 완료, 어드민 메뉴 인증 문제 해결, 토큰 인증 문제 완전 해결)
|
||||
|
||||
Reference in New Issue
Block a user