백엔드가 Spring Security OAuth2 + JWT + Redis를 사용하여 인증을 처리합니다.
- Exchange Code 패턴: OAuth 리다이렉트에서 토큰 직접 노출 방지 (30초 TTL)
- HttpOnly Cookie: XSS 공격으로부터 Refresh Token 보호
- Refresh Token Rotation: 토큰 재사용 공격 탐지 및 차단
- Redis TTL: 자동 토큰 만료 관리
프론트엔드 (/login)
↓ "카카오로 시작하기" 클릭
백엔드 (http://localhost:8080/oauth2/authorization/kakao)
↓ 자동 리다이렉트
Kakao OAuth 페이지
↓ 사용자 인증
백엔드 (http://localhost:8080/login/oauth2/code/kakao?code=XXX)
↓ OAuth2LoginSuccessHandler 처리
프론트엔드 (http://localhost:3000/auth/success?code=exchangeCode)
Exchange Code: 일회성 교환 코드 (30초 TTL, Redis 저장)
프론트엔드 → POST /api/v1/auth/token
Body: { "code": "exchangeCode" }
응답:
{
"code": "SUCCESS",
"message": "토큰 발급에 성공하였습니다.",
"data": {
"access_token": "eyJhbGci...",
"expires_in": 1800,
"is_new_user": false
}
}
+ Set-Cookie: refresh_token=...; HttpOnly; Secure; SameSite=Lax
Access Token: JSON 응답 (30분 유효) Refresh Token: HttpOnly Cookie (14일 유효)
프론트엔드 → API 요청
Header: Authorization: Bearer {accessToken}
Cookie: refresh_token=... (자동 전송)
→ JwtAuthenticationFilter (토큰 검증)
→ SecurityContext에 인증 정보 저장
→ Controller
프론트엔드 → POST /api/v1/auth/refresh
Cookie: refresh_token={refreshToken} (자동 전송)
응답:
- 새 Access Token (JSON)
- 새 Refresh Token (Cookie, 기존 토큰 무효화)
Rotation 방식: 매번 새 Refresh Token 발급, 기존 토큰 삭제 재사용 탐지: 이미 사용된 토큰 재사용 시 모든 토큰 삭제
프론트엔드 → POST /api/v1/auth/logout
→ Redis에서 Refresh Token 삭제
→ 쿠키 만료 처리
→ localStorage에서 Access Token 삭제
src/types/auth.ts- TypeScript 타입 정의src/lib/auth/tokenStorage.ts- Access Token 저장/조회/삭제src/lib/api/client.ts- API 클라이언트 (credentials: 'include')src/lib/api/auth.ts- 인증 API 호출src/contexts/AuthContext.tsx- 전역 인증 상태 관리src/app/auth/success/page.tsx- OAuth 성공 후 콜백 처리src/components/auth/ProtectedRoute.tsx- 인증 필요 컴포넌트
src/app/layout.tsx- AuthProvider 추가src/app/(main)/layout.tsx- ProtectedRoute 적용src/app/(auth)/login/page.tsx- 백엔드 OAuth URL로 리다이렉트src/app/(auth)/nickname/page.tsx- 닉네임 저장 API 호출src/app/(auth)/lifestyle-tags/page.tsx- 태그 저장 API 호출
- 저장: localStorage (tokenStorage)
- 사용: 모든 API 요청 헤더에 포함
headers: { 'Authorization': `Bearer ${accessToken}` }
- 유효기간: 30분
- 갱신: Refresh Token으로 재발급
- 저장: HttpOnly Cookie (백엔드 자동 설정)
- 사용: 브라우저가 자동으로 쿠키 전송
- 유효기간: 14일
- 보안: JavaScript 접근 불가 (XSS 방어)
모든 API 요청에 쿠키를 자동으로 포함하도록 설정:
fetch(url, {
credentials: 'include',
headers: { ... }
})/login→ "카카오로 시작하기" 클릭- 백엔드 OAuth URL로 리다이렉트
- Kakao 인증 후 백엔드 처리
/auth/success?code=exchangeCode리다이렉트- 프론트엔드가 code로 토큰 발급
is_new_user: true확인/nickname→ 닉네임 입력/lifestyle-tags→ 태그 선택/home→ 메인 페이지
/login→ "카카오로 시작하기" 클릭- 백엔드 OAuth URL로 리다이렉트
- Kakao 인증 후 백엔드 처리
/auth/success?code=exchangeCode리다이렉트- 프론트엔드가 code로 토큰 발급
is_new_user: false확인/home→ 바로 메인 페이지
/oauth2/**,/login/**/api/v1/auth/token,/api/v1/auth/refresh
POST /api/v1/auth/token
Content-Type: application/json
{
"code": "exchangeCode"
}
→ 200 OK
{
"code": "SUCCESS",
"message": "토큰 발급에 성공하였습니다.",
"data": {
"access_token": "eyJhbGci...",
"expires_in": 1800,
"is_new_user": false
}
}
Set-Cookie: refresh_token=...; HttpOnly; Secure; SameSite=LaxPOST /api/v1/auth/refresh
Cookie: refresh_token=...
→ 200 OK
{
"code": "SUCCESS",
"data": {
"access_token": "eyJhbGci...",
"expires_in": 1800
}
}
Set-Cookie: refresh_token=...; HttpOnly; Secure; SameSite=Lax (새 토큰)POST /api/v1/auth/logout
Authorization: Bearer {accessToken}
Cookie: refresh_token=...
→ 200 OK
Set-Cookie: refresh_token=; Max-Age=0 (쿠키 삭제)PATCH /api/v1/members/nickname
Authorization: Bearer {accessToken}
Content-Type: application/json
{
"nickname": "사용자닉네임"
}POST /api/v1/lifestyles
Authorization: Bearer {accessToken}
Content-Type: application/json
{
"tags": ["신혼부부", "반려동물", "재택근무"]
}npm run dev백엔드 API 서버가 http://localhost:8080에서 실행 중이어야 합니다.
http://localhost:3000/login접속- "카카오로 시작하기" 클릭
http://localhost:8080/oauth2/authorization/kakao로 리다이렉트 확인- Kakao OAuth 페이지로 이동 확인
- 인증 후
/auth/success?code=XXX로 리다이렉트 확인 - Network 탭에서
POST /api/v1/auth/token호출 확인 - 응답에서
access_token확인 - Application → Cookies에서
refresh_token확인
- OAuth 로그인 완료 후
/nickname페이지로 이동 확인 - 닉네임 입력 후 "다음" 클릭
- Network 탭에서
PATCH /v1/members/nickname호출 확인 - Request Headers에
Authorization: Bearer ...확인 /lifestyle-tags페이지로 이동 확인- 태그 선택 후 "완료" 클릭
- Network 탭에서
POST /v1/lifestyles호출 확인 /home페이지로 이동 확인
- OAuth 로그인 완료 후 바로
/home으로 이동 확인
- Access Token: Application → Local Storage →
auth_token - Refresh Token: Application → Cookies →
refresh_token- HttpOnly: ✓ (JavaScript로 접근 불가)
- Secure: ✓ (HTTPS만)
- SameSite: Lax
- 로그인 후 페이지 새로고침
- 로그인 상태 유지 확인
- localStorage에서 토큰 읽어서 복원
NEXT_PUBLIC_API_URL=http://localhost:8080/api
NEXT_PUBLIC_APP_URL=http://localhost:3000주의: Kakao OAuth 설정은 백엔드에서 관리합니다. 프론트엔드에서는 불필요합니다.
src/
├── types/
│ └── auth.ts
├── lib/
│ ├── auth/
│ │ └── tokenStorage.ts
│ └── api/
│ ├── client.ts
│ └── auth.ts
├── contexts/
│ └── AuthContext.tsx
├── components/
│ └── auth/
│ ├── ProtectedRoute.tsx
│ └── index.ts
└── app/
├── layout.tsx (AuthProvider)
├── auth/
│ └── success/
│ └── page.tsx (OAuth 콜백)
├── (auth)/
│ ├── login/
│ │ └── page.tsx (백엔드 OAuth URL로 리다이렉트)
│ ├── nickname/
│ │ └── page.tsx (API 연동)
│ └── lifestyle-tags/
│ └── page.tsx (API 연동)
└── (main)/
└── layout.tsx (ProtectedRoute)
✅ Exchange Code 패턴 (일회성 코드, 30초 TTL) ✅ HttpOnly Cookie (XSS 방어) ✅ Refresh Token Rotation (재사용 공격 방어) ✅ credentials: 'include' (쿠키 자동 전송) ✅ JWT 토큰 검증 (백엔드)
- HTTPS 적용 (프로덕션 필수)
- CORS 설정 확인
- Access Token도 메모리에 저장 (localStorage 대신)
- 토큰 만료 시 자동 리프레시 로직
- 토큰 자동 갱신: Access Token 만료 시 자동으로 Refresh Token으로 갱신
- 로그아웃 기능: 마이페이지에서 로그아웃 버튼 추가
- 에러 처리 개선: 토큰 만료, 네트워크 오류 등 사용자 친화적 메시지
- 로딩 상태 개선: 스켈레톤 UI 추가