로그인은 그냥 토큰만 쓰면 안되나요...
1. 인증 방식, 무엇을 선택할까?
Spring Security 설정하다 보면 금방 맞닥뜨린다. Session이냐 JWT냐.
찾아보면 JWT가 요즘 대세처럼 보인다. Stateless, 확장성, MSA 친화적... 근데 직접 요구사항을 따져보니 JWT가 딱 맞지 않는 지점이 있었다.
2. Session vs Token 비교
Session
전통적인 웹 서비스 방식이다. 서버가 "모든 것을 기억" 한다.
- 저장: 서버의 메모리(혹은 외부 저장소)에 로그인/인증 정보를 저장한다.
- 전송: 클라이언트는
JSESSIONID와 같은 세션 ID만 쿠키로 들고 다닌다. - 검증: 요청이 올 때마다 서버는 세션 ID를 통해 자신이 가지고 있는 정보와 매칭하여 유효성을 검사한다.
서버가 상태를 직접 쥐고 있어서 강제 로그아웃, 계정 차단을 즉각 처리할 수 있다. Spring Security 기본 방식이라 설정도 단순하다.
약점은 확장성이다. 서버를 여러 대 두면 각자 세션이 따로 놀아서 스케일아웃이 안 된다. Redis 같은 공유 저장소가 없으면.
Token (JWT)
MSA 환경에서 주로 쓰는 방식이다. 서버는 "검증만 할 뿐, 기억하지 않는다."
- 발급: 로그인 성공 시 서버가 사용자 정보가 담긴 암호화된 토큰(JWT) 을 발급한다.
- 전송: 클라이언트는 요청 시 헤더(
Authorization: Bearer ...)에 토큰을 담아 보낸다. - 검증: 서버는 토큰의 서명(Signature) 만 확인하여 위변조 여부를 체크한다. (별도 저장소 조회 X)
서버에 아무것도 저장하지 않아서 서버를 얼마든지 늘릴 수 있다. 모바일 앱, SPA, SSO 환경에 자연스럽게 맞는다.
문제는 "발급된 토큰은 뺏을 수 없다" 는 것이다. 탈취되든, 강제 로그아웃을 시켜야 하든, 만료 전까지는 방법이 없다. Blacklist로 해결한다고들 하는데 그러면 어차피 Redis 같은 외부 저장소가 필요하다. 그 순간부터 Stateless 장점이 흔들린다.
3. 정리하면
| 구분 | Session 권장 | Token (JWT) 권장 |
|---|---|---|
| 서비스 유형 | 전통적 웹 서비스, 내부 어드민 시스템 | MSA, 모바일 앱, SPA, 글로벌 서비스 |
| 인프라 환경 | 단일 서버 혹은 소규모 클러스터 | 오토스케일링이 잦은 클라우드/분산 환경 |
| 필요 기능 | 강제 로그아웃, 실시간 동시 접속 제어 | SSO, 다양한 클라이언트(Web/App) 지원 |
| 핵심 가치 | 보안 및 중앙 제어 (Control) | 확장성 및 유연성 (Scalability) |
웹 기반 내부 서비스에는 Session, 대규모 분산 환경이나 다양한 클라이언트를 지원해야 하면 Token.
4. Session + Redis 조합
왜 Session을 선택했나?
이전 프로젝트에서 관리자가 악성 사용자를 즉시 강제 로그아웃시킬 수 있어야 했다. 이게 결정적이었다.
JWT로 구현하면 Blacklist가 필요하고, Blacklist를 관리하려면 어차피 Redis가 필요하다. 그러면 Session + Redis를 쓰는 것과 뭐가 다른가. 같은 수고를 들이면서 JWT의 장점은 없어지는 구조다.
Same Origin 환경이었고 Spring Security 기본 설정이 그대로 맞았다. JWT를 억지로 얹을 이유가 없었다.
단점 극복: Redis로 세션 클러스터링
세션 방식의 치명적인 약점은 서버 간 세션 불일치다. WAS를 2대 이상 늘리면, A 서버에서 로그인한 사용자가 B 서버로 요청을 보낼 때 로그인이 풀린다.
해결책은 각 서버의 로컬 메모리 대신 Redis에 세션을 저장하는 것이다. Spring Session Data Redis를 쓰면 설정 몇 줄로 끝난다. 어느 서버로 요청이 오든 Redis에서 동일한 세션을 참조하므로 로그인 상태가 유지된다.
세션의 보안 제어는 챙기면서, 확장성 문제는 Redis로 해결하는 구조다.
5. 마치며
Session이냐 JWT냐보다, 내 서비스에서 "강제 로그아웃이 필요한가" 가 실제로 더 중요한 질문이었다. 그 답이 Yes라면 JWT는 Blacklist 없이는 구현이 어렵고, Blacklist를 쓰면 어차피 외부 저장소가 필요해진다. 그러면 Session + Redis가 더 솔직한 선택이다.