관리자 메뉴 토큰문제 수정정

This commit is contained in:
kjs
2025-08-21 13:28:49 +09:00
parent a0e5b57a24
commit 71d34ffd88
18 changed files with 1473 additions and 254 deletions

View File

@@ -836,6 +836,180 @@ export const logger = winston.createLogger({
11. **메뉴 API 완료**: `/api/admin/menus``/api/admin/user-menus` API가 성공적으로 구현되어 프론트엔드 메뉴 표시가 정상 작동
12. **JWT 토큰 관리**: 프론트엔드 API 클라이언트에서 JWT 토큰을 자동으로 포함하여 인증 문제 해결
13. **환경변수 관리**: Prisma 스키마에서 직접 데이터베이스 URL 설정으로 환경변수 로딩 문제 해결
14. **어드민 메뉴 인증**: 새 탭에서 열리는 어드민 페이지의 토큰 인증 문제 해결 - localStorage 공유 활용
## 🔐 인증 및 보안 가이드
### 어드민 메뉴 토큰 인증 문제 해결
#### 문제 상황
- 어드민 버튼 클릭 시 새 탭에서 어드민 페이지가 열림
- 새 탭에서 토큰 인증 문제 발생 가능성
- URL 파라미터로 토큰 전달은 보안상 위험
#### 해결 방안 (권장)
**1. localStorage 공유 활용 (가장 간단)**
```typescript
// AdminButton.tsx - 수정 없음
const handleAdminClick = () => {
const adminUrl = `${window.location.origin}/admin`;
window.open(adminUrl, "_blank");
};
// admin/page.tsx - AuthGuard 적용
("use client");
import { AuthGuard } from "@/components/auth/AuthGuard";
import { CompanyManagement } from "@/components/admin/CompanyManagement";
export default function AdminPage() {
return (
<AuthGuard requireAdmin={true}>
<CompanyManagement />
</AuthGuard>
);
}
```
**2. BroadcastChannel API 활용 (고급)**
```typescript
// utils/tabCommunication.ts
export class TabCommunication {
private channel: BroadcastChannel;
constructor() {
this.channel = new BroadcastChannel("auth-channel");
}
// 토큰 요청
requestToken(): Promise<string | null> {
return new Promise((resolve) => {
const timeout = setTimeout(() => {
resolve(localStorage.getItem("authToken"));
}, 100);
this.channel.postMessage({ type: "REQUEST_TOKEN" });
const handler = (event: MessageEvent) => {
if (event.data.type === "TOKEN_RESPONSE") {
clearTimeout(timeout);
this.channel.removeEventListener("message", handler);
resolve(event.data.token);
}
};
this.channel.addEventListener("message", handler);
});
}
}
```
**3. 쿠키 기반 토큰 (가장 안전)**
```typescript
// backend-node/src/controllers/authController.ts
static async login(req: Request, res: Response): Promise<void> {
// HTTPOnly 쿠키로 토큰 설정
res.cookie('authToken', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000, // 24시간
});
}
```
#### 보안 고려사항
1. **URL 파라미터 사용 금지**: 토큰이 URL에 노출되어 보안 위험
2. **HTTPS 필수**: 프로덕션 환경에서는 반드시 HTTPS 사용
3. **토큰 만료 처리**: 자동 갱신 또는 재로그인 유도
4. **CSRF 방지**: 토큰 기반 요청 검증
5. **로그아웃 처리**: 모든 탭에서 토큰 제거
#### 구현 우선순위
**1단계 (즉시 적용)**
- AuthGuard를 사용한 어드민 페이지 보호
- localStorage 공유 활용
**2단계 (1-2일 내)**
- 토큰 유효성 검증 API 추가
- 에러 처리 개선
**3단계 (3-5일 내)**
- 세션 관리 개선
- 토큰 갱신 로직 추가
### JWT 토큰 관리 모범 사례
#### 프론트엔드 토큰 관리
```typescript
// lib/api/client.ts
const TokenManager = {
getToken: (): string | null => {
if (typeof window !== "undefined") {
return localStorage.getItem("authToken");
}
return null;
},
isTokenExpired: (token: string): boolean => {
try {
const payload = JSON.parse(atob(token.split(".")[1]));
return payload.exp * 1000 < Date.now();
} catch {
return true;
}
},
};
```
#### 백엔드 토큰 검증
```typescript
// middleware/authMiddleware.ts
export const authenticateToken = (
req: AuthenticatedRequest,
res: Response,
next: NextFunction
): void => {
try {
const authHeader = req.get("Authorization");
const token = authHeader && authHeader.split(" ")[1];
if (!token) {
res.status(401).json({
success: false,
error: {
code: "TOKEN_MISSING",
details: "인증 토큰이 필요합니다.",
},
});
return;
}
const userInfo: PersonBean = JwtUtils.verifyToken(token);
req.user = userInfo;
next();
} catch (error) {
res.status(401).json({
success: false,
error: {
code: "INVALID_TOKEN",
details: "토큰 검증에 실패했습니다.",
},
});
}
};
```
## 🎯 성공 지표
@@ -847,6 +1021,6 @@ export const logger = winston.createLogger({
---
**마지막 업데이트**: 2024년 12월 20일
**버전**: 1.7.0
**버전**: 1.8.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 구현 완료, 어드민 메뉴 인증 문제 해결)