178 lines
4.6 KiB
Markdown
178 lines
4.6 KiB
Markdown
|
|
# Middleware 인증 문제 해결 보고서
|
||
|
|
|
||
|
|
## 📅 작성일: 2025-11-07
|
||
|
|
|
||
|
|
## 🔍 문제 증상
|
||
|
|
|
||
|
|
로그인하지 않은 상태에서 `/dashboard`에 접근 시, 인증 체크가 작동하지 않고 대시보드에 바로 접근되는 문제가 발생했습니다.
|
||
|
|
|
||
|
|
### 증상 상세
|
||
|
|
- ✅ 로그인/로그아웃 기능 정상 작동
|
||
|
|
- ✅ 쿠키(`user_token`) 저장/삭제 정상
|
||
|
|
- ❌ Middleware에서 보호된 라우트 접근 차단 실패
|
||
|
|
- ❌ Middleware console.log가 터미널에 전혀 출력되지 않음
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🐛 발견된 문제들
|
||
|
|
|
||
|
|
### 1. Next.js 15 + next-intl 호환성 문제
|
||
|
|
**위치**: `next.config.ts`
|
||
|
|
|
||
|
|
**원인**:
|
||
|
|
- Next.js 15에서 next-intl v4를 사용할 때 `turbopack` 설정이 필수
|
||
|
|
- 이 설정이 없으면 middleware가 제대로 컴파일되지 않음
|
||
|
|
|
||
|
|
**해결**:
|
||
|
|
```typescript
|
||
|
|
// next.config.ts
|
||
|
|
const nextConfig: NextConfig = {
|
||
|
|
turbopack: {}, // ✅ 추가
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2. 복잡한 Matcher 정규식
|
||
|
|
**위치**: `src/middleware.ts` - `config.matcher`
|
||
|
|
|
||
|
|
**원인**:
|
||
|
|
- 너무 복잡한 regex 패턴으로 라우트 매칭 실패
|
||
|
|
- 중복된 matcher 패턴 (정규식 + 명시적 경로)
|
||
|
|
|
||
|
|
**기존 코드**:
|
||
|
|
```typescript
|
||
|
|
matcher: [
|
||
|
|
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico)$).*)',
|
||
|
|
'/dashboard/:path*',
|
||
|
|
'/login',
|
||
|
|
'/register',
|
||
|
|
]
|
||
|
|
```
|
||
|
|
|
||
|
|
**해결**:
|
||
|
|
```typescript
|
||
|
|
matcher: [
|
||
|
|
'/((?!api|_next/static|_next/image|favicon.ico|.*\\..*|robots\\.txt).*)',
|
||
|
|
]
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 3. isPublicRoute 함수 로직 버그 ⭐ (핵심 문제)
|
||
|
|
**위치**: `src/middleware.ts` - `isPublicRoute()` 함수
|
||
|
|
|
||
|
|
**원인**:
|
||
|
|
```typescript
|
||
|
|
// 문제 코드
|
||
|
|
function isPublicRoute(pathname: string): boolean {
|
||
|
|
return AUTH_CONFIG.publicRoutes.some(route =>
|
||
|
|
pathname === route || pathname.startsWith(route)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**버그 시나리오**:
|
||
|
|
1. `AUTH_CONFIG.publicRoutes`에 `'/'` 포함
|
||
|
|
2. `/dashboard`.startsWith('/') → `true` 반환
|
||
|
|
3. 모든 경로가 public route로 잘못 판단됨
|
||
|
|
4. 인증 체크가 스킵되어 보호된 라우트 접근 가능
|
||
|
|
|
||
|
|
**해결**:
|
||
|
|
```typescript
|
||
|
|
function isPublicRoute(pathname: string): boolean {
|
||
|
|
return AUTH_CONFIG.publicRoutes.some(route => {
|
||
|
|
// '/' 는 정확히 일치해야만 public
|
||
|
|
if (route === '/') {
|
||
|
|
return pathname === '/';
|
||
|
|
}
|
||
|
|
// 다른 라우트는 시작 일치 허용
|
||
|
|
return pathname === route || pathname.startsWith(route + '/');
|
||
|
|
});
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**수정 후 동작**:
|
||
|
|
- `/` → public ✅
|
||
|
|
- `/dashboard` → protected ✅
|
||
|
|
- `/about` → public ✅
|
||
|
|
- `/about/team` → public ✅
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ✅ 해결 결과
|
||
|
|
|
||
|
|
### 적용된 수정 사항
|
||
|
|
1. ✅ `next.config.ts`에 `turbopack: {}` 추가
|
||
|
|
2. ✅ Middleware matcher 단순화
|
||
|
|
3. ✅ `isPublicRoute()` 함수 로직 수정
|
||
|
|
4. ✅ 디버깅 로그 제거 (클린 코드)
|
||
|
|
|
||
|
|
### 검증 결과
|
||
|
|
```bash
|
||
|
|
# 로그아웃 상태에서 /dashboard 접근 시:
|
||
|
|
[Auth Required] Redirecting to /login from /dashboard
|
||
|
|
→ 자동으로 /login 페이지로 리다이렉트 ✅
|
||
|
|
|
||
|
|
# 로그인 상태에서 /dashboard 접근 시:
|
||
|
|
[Authenticated] Mode: bearer, Path: /dashboard
|
||
|
|
→ 정상 접근 ✅
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📝 교훈
|
||
|
|
|
||
|
|
### 1. Middleware 디버깅
|
||
|
|
- **브라우저 콘솔이 아닌 서버 터미널**에서 로그 확인
|
||
|
|
- `console.log`는 서버 사이드에서 실행되므로 터미널 출력
|
||
|
|
|
||
|
|
### 2. 문자열 매칭 주의
|
||
|
|
- `startsWith('/')` 같은 패턴은 모든 경로와 매칭됨
|
||
|
|
- Root path(`/`)는 항상 정확한 일치(`===`) 사용
|
||
|
|
|
||
|
|
### 3. Next.js 버전별 설정
|
||
|
|
- Next.js 15 + next-intl 사용 시 `turbopack` 설정 필수
|
||
|
|
- 공식 문서 및 마이그레이션 가이드 확인 필요
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔗 관련 파일
|
||
|
|
|
||
|
|
### 수정된 파일
|
||
|
|
- `next.config.ts` - turbopack 설정 추가
|
||
|
|
- `src/middleware.ts` - isPublicRoute 로직 수정, matcher 단순화
|
||
|
|
|
||
|
|
### 관련 설정 파일
|
||
|
|
- `src/lib/api/auth/auth-config.ts` - 라우트 설정
|
||
|
|
- `src/lib/api/auth/sanctum-client.ts` - 인증 로직
|
||
|
|
- `src/lib/api/auth/token-storage.ts` - 토큰 관리
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎯 현재 인증 플로우
|
||
|
|
|
||
|
|
### 로그인
|
||
|
|
1. 사용자가 `/login`에서 인증 정보 입력
|
||
|
|
2. PHP API(`/api/v1/login`)로 요청 (API Key 포함)
|
||
|
|
3. Bearer Token 발급 (`user_token`)
|
||
|
|
4. localStorage 저장 + Cookie 동기화
|
||
|
|
5. `/dashboard`로 리다이렉트
|
||
|
|
|
||
|
|
### 보호된 라우트 접근
|
||
|
|
1. Middleware에서 요청 가로채기
|
||
|
|
2. Cookie에서 `user_token` 확인
|
||
|
|
3. 토큰 있음 → 통과
|
||
|
|
4. 토큰 없음 → `/login`으로 리다이렉트
|
||
|
|
|
||
|
|
### 로그아웃
|
||
|
|
1. PHP API(`/api/v1/logout`) 호출
|
||
|
|
2. localStorage 및 Cookie 정리
|
||
|
|
3. `/login`으로 리다이렉트
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📚 참고 자료
|
||
|
|
- Next.js 15 Middleware 공식 문서
|
||
|
|
- next-intl v4 마이그레이션 가이드
|
||
|
|
- `claudedocs/research_nextjs15_middleware_authentication_2025-11-07.md`
|