1. 들어가며
BenePicker 프로젝트를 진행하면서 인증을 JWT 기반으로 설계했다.
로그인, 토큰
발급까지는 비교적 자연스럽게 구현됐지만, 로그아웃 구현에서 예상 밖의 혼란을 겪었다.
"로그아웃인데… 왜 서버에서 아무것도 안 하지?"

이번 글에서는 단순 구현이 아니라, JWT 로그아웃의 본질과 설계 관점에서의 깨달음을 정리해보려고 한다.
2. 구현한 로그아웃 코드
@PostMapping("/logout")
public ResponseEntity<Void> logout() {
return ResponseEntity.ok().build();
}
처음 이 코드를 작성하면서 이런 생각이 들었다.
“이게… 로그아웃 맞나?”
로직이 없다.
DB 접근도 없다.
토큰 처리도 없다.
그냥 200 OK 반환 끝이다.
3. 왜 이렇게 구현했을까?
이걸 이해하려면 먼저 JWT의 핵심 개념을 알아야 한다.
1) JWT의 본질: Stateless
JWT 인증은 Stateless(무상태) 구조다.
즉,
서버는 로그인 상태를 저장하지 않는다.
2) 우리가 익숙한 방식 (세션)
- 로그인 → 서버에 세션 저장
- 로그아웃 → 서버에서 세션 삭제
서버가 상태를 관리한다
3) JWT 방식
- 로그인 → 토큰 발급
- 이후 요청 → 토큰만 검증
서버는 상태를 저장하지 않는다
4) 핵심 깨달음
그래서 결론은
JWT에서 로그아웃 = 서버 작업이 아니다
4. 실제 로그아웃 흐름
JWT 기반 로그아웃은 이렇게 동작한다.
2. 서버 → 200 OK 응답
3. 클라이언트 → 토큰 삭제
4. 이후 요청 → 인증 실패 (401)
즉, 서버는 그냥 “응답만 해주는 역할”
5. 진짜 로그아웃은 어디서 일어날까?
정답: 클라이언트
- LocalStorage / AsyncStorage에서 토큰 삭제
- 사용자 상태 초기화
- 로그인 페이지로 이동
이걸 안 하면?
→ 로그아웃 API를 호출해도 계속 로그인 상태처럼 보인다
6. 설계 관점에서의 인사이트
이번 경험에서 가장 크게 느낀 점
"JWT는 서버를 단순하게 만드는 대신, 클라이언트 책임을 증가시킨다"
7. 현재 구조의 한계
지금 구현은 간단하고 효율적이지만,
실제 서비스에서는 문제가 될 수 있다.
문제점
- 탈취된 토큰은 만료 전까지 계속 사용 가능
- 서버에서 강제 로그아웃 불가능
8. 실무에서는 어떻게 해결할까?
실제 서비스에서는 보통 아래 방식들을 추가로 사용한다.
1) Refresh Token 관리
- 서버(DB)에 저장
- 로그아웃 시 삭제
2) Redis 블랙리스트
→ 이후 요청 차단
3) 토큰 만료 시간 전략
- AccessToken: 짧게
- RefreshToken: 길게
9. 회고
이번 로그아웃 구현은 단순한 기능 하나를 넘어서, 인증 구조 자체를 다시 이해하게 되는 계기였다.
처음에는
"왜 동작을 안하지?"
라고 생각했지만, 지금은 오히려 이렇게 느낀다.
"JWT는 설계를 이해하지 않으면 절대 제대로 쓸 수 없다"
한 줄 정리
JWT에서 로그아웃은 서버가 아니라 클라이언트가 수행한다
다음 목표
- Refresh Token 기반 인증 구조 개선
- Redis 블랙리스트 도입
- 로그아웃 시 토큰 무효화 처리
'백엔드 > 회고' 카테고리의 다른 글
| [Spring] MyBatis vs JPA + XML vs Annotation, 직접 써보며 느낀 차이 (0) | 2026.04.03 |
|---|---|
| [Swagger] Spring Boot Swagger 초기 설정 (feat. JWT 403 삽질기) (0) | 2026.03.31 |
| [GitHub] 협업 효율을 높여준 PR & Issue 템플릿 도입 (0) | 2026.03.31 |