백엔드/회고

[Spring] JWT 로그아웃 기본 코드 분석

miniberry 2026. 4. 3. 13:24

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 기반 로그아웃은 이렇게 동작한다.

1. 클라이언트 → /logout 요청
2. 서버 → 200 OK 응답
3. 클라이언트 → 토큰 삭제
4. 이후 요청 → 인증 실패 (401)
 

즉, 서버는 그냥 “응답만 해주는 역할”


5. 진짜 로그아웃은 어디서 일어날까?

정답: 클라이언트

  • LocalStorage / AsyncStorage에서 토큰 삭제
  • 사용자 상태 초기화
  • 로그인 페이지로 이동

이걸 안 하면?

로그아웃 API를 호출해도 계속 로그인 상태처럼 보인다


6. 설계 관점에서의 인사이트

이번 경험에서 가장 크게 느낀 점

"JWT는 서버를 단순하게 만드는 대신, 클라이언트 책임을 증가시킨다"


7. 현재 구조의 한계

지금 구현은 간단하고 효율적이지만,
실제 서비스에서는 문제가 될 수 있다.

 문제점

  • 탈취된 토큰은 만료 전까지 계속 사용 가능
  • 서버에서 강제 로그아웃 불가능

8. 실무에서는 어떻게 해결할까?

실제 서비스에서는 보통 아래 방식들을 추가로 사용한다.


 1) Refresh Token 관리

  • 서버(DB)에 저장
  • 로그아웃 시 삭제

 2) Redis 블랙리스트

로그아웃된 AccessToken → Redis에 저장
 

이후 요청 차단


 3) 토큰 만료 시간 전략

  • AccessToken: 짧게
  • RefreshToken: 길게

9. 회고

이번 로그아웃 구현은 단순한 기능 하나를 넘어서, 인증 구조 자체를 다시 이해하게 되는 계기였다.

처음에는

"왜 동작을 안하지?"

라고 생각했지만, 지금은 오히려 이렇게 느낀다.

"JWT는 설계를 이해하지 않으면 절대 제대로 쓸 수 없다"


 한 줄 정리

JWT에서 로그아웃은 서버가 아니라 클라이언트가 수행한다


 다음 목표

  • Refresh Token 기반 인증 구조 개선
  • Redis 블랙리스트 도입
  • 로그아웃 시 토큰 무효화 처리