[JWT] JWT 인증 시스템 가이드
JWT 구조
JWT는 3개의 파트로 구성됩니다:
text
header.payload.signature예시:
text
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1YWJjZDEyMzQ1Njc4OTAiLCJ1c2VybmFtZSI6ImpvaG4iLCJwcm92aWRlciI6ImxvY2FsIiwiZW1haWwiOiJqb2huQGV4YW1wbGUuY29tIiwiaWF0IjoxNzAwMDAwMDAwLCJleHAiOjE3MDAwMDA5MDB9.서명부분중요: JWT의 payload는 암호화가 아닌 인코딩입니다. 누구나 디코딩해서 내용을 볼 수 있으며, signature 부분만 JWT_SECRET으로 서명되어 위변조 방지 역할을 합니다.
JWT Payload 내용
이 프로젝트의 Access Token payload:
| 필드 | 설명 |
|---|---|
| id | 사용자의 MongoDB ObjectId |
| username | 사용자명 |
| provider | 인증 방식 (local, google, apple 등) |
| 이메일 주소 | |
| exp | 토큰 만료 시간 |
| iat | 토큰 발급 시간 |
토큰 만료 시간 설정
| 토큰 | 환경변수 | 기본값 |
|---|---|---|
| Access Token | SESSION_EXPIRY | 15분 |
| Refresh Token | REFRESH_TOKEN_EXPIRY | 7일 |
이중 토큰 시스템 (Access + Refresh)
Access Token
- 짧은 만료 시간 (15분)
- 클라이언트 메모리에 저장
- 모든 API 요청의 Authorization 헤더에 포함
Refresh Token
- 긴 만료 시간 (7일)
- httpOnly 쿠키로 저장 (JavaScript 접근 불가)
- secure 플래그 (HTTPS에서만 전송)
- sameSite=strict (CSRF 방어)
- Access Token 갱신에만 사용
토큰 갱신 흐름 (서버 응답 기반)
text
클라이언트 API 요청
↓
서버: "토큰 만료됨" (401 Unauthorized)
↓
axios interceptor가 401 감지
↓
자동으로 /api/auth/refresh 호출 (Refresh Token 쿠키 포함)
↓
새 Access Token 수신
↓
원래 요청 자동 재시도
↓
사용자에게 결과 전달 (seamless)서버 응답 기반 vs 클라이언트 선제 확인
| 항목 | 서버 응답 기반 | 클라이언트 선제 확인 |
|---|---|---|
| 구현 복잡도 | 단순 | 복잡 (JWT 디코딩, 시간 계산) |
| 시간 동기화 | 서버 시계만 기준 | 클라이언트 시계 의존 (오차 가능) |
| 서버 정책 변경 | 클라이언트 수정 불필요 | 수정 필요할 수 있음 |
| 토큰 강제 만료 대응 | 즉시 대응 | exp 기준이라 대응 불가 |
| 네트워크 효율 | 실패 시 재요청 필요 | 미리 갱신하면 재요청 없음 |
보안 관점
JWT 탈취 시 위험
JWT는 stateless 방식이라 탈취 시 만료 전까지 사용 가능합니다.
보안 대책
- 짧은 Access Token 만료 시간: 15분으로 피해 범위 제한
- Refresh Token 보호:
- httpOnly: XSS 공격으로 JavaScript 접근 불가
- secure: HTTPS에서만 전송
- sameSite=strict: CSRF 공격 방어
- 세션 기반 Refresh Token: DB에서 세션 삭제 시 갱신 차단
Refresh Token 탈취 난이도
Refresh Token을 탈취하려면:
- 피해자 기기에 물리적 접근
- 네트워크 MITM 공격 (HTTPS라 어려움)
- 브라우저 취약점 악용
- 악성 브라우저 확장 프로그램
로그아웃 처리
javascript
const logoutUser = async (req, refreshToken) => {
// 1. DB에서 세션 삭제
const session = await findSession({ userId, refreshToken });
await deleteSession({ sessionId: session._id });
// 2. Express 세션 파괴
req.session.destroy();
};| 항목 | 위치 | 처리 |
|---|---|---|
| Refresh Token 세션 | 서버 DB | deleteSession()으로 삭제 |
| 클라이언트 쿠키 | 브라우저 | 서버가 만료된 쿠키로 덮어씀 |
| Access Token | 클라이언트 메모리 | 상태 초기화 |
로그아웃 후에는 탈취된 Refresh Token도 DB에 세션이 없어 무효화됩니다.
자동 로그인
"자동 로그인" = Refresh Token 만료 시간이 길게 설정됨
| 서비스 유형 | Refresh Token 만료 | 특징 |
|---|---|---|
| 은행/금융 | 30분 ~ 1일 | 보안 중시 |
| 일반 웹서비스 | 7일 ~ 30일 | 적당한 균형 |
| SNS/스트리밍 | 30일 ~ 1년 | 편의성 중시 |
자동 로그인 흐름
text
Day 1: 로그인 → Refresh Token (7일 유효)
Day 2: 브라우저 열기 → Access Token 없음 → Refresh로 갱신 → 로그인 유지
Day 3: 같은 방식...
Day 7: Refresh Token 만료 → 다시 로그인 필요"로그인 유지" 체크박스 구현 방식
javascript
// 방법 1: 만료 시간 분기
if (rememberMe) {
expiration = 30일;
} else {
expiration = 1일;
}
// 방법 2: 세션 쿠키 사용
if (rememberMe) {
cookie.expires = 30일;
} else {
cookie.expires = undefined; // 브라우저 닫으면 삭제
}관련 파일 위치
| 파일 | 역할 |
|---|---|
| api/models/userMethods.js | Access Token 생성 (generateToken) |
| api/models/Session.js | Refresh Token 및 세션 관리 |
| api/server/services/AuthService.js | 인증 서비스 (로그인, 로그아웃, 토큰 설정) |
| api/server/controllers/AuthController.js | 인증 컨트롤러 (refresh 엔드포인트) |
| packages/data-provider/src/request.ts | 클라이언트 axios interceptor (자동 토큰 갱신) |
댓글을 불러오는 중...