"사용자가 아이디/비번을 입력하고 로그인에 성공했습니다. 그런데 페이지를 이동할 때마다 아이디/비번을 계속 물어볼 순 없습니다. 이 사용자가 아까 그 사용자라는 것을 어떻게 기억해야 할까요?"
HTTP는 상태가 없는(Stateless) 프로토콜입니다. 따라서 우리는 상태(State)를 유지하기 위한 기술이 필요합니다. 이때 "서버가 기억하느냐(Stateful)", **"클라이언트가 들고 다니느냐(Stateless)"**에 따라 시스템 구조가 완전히 달라집니다.
1. 방식 1: 세션 기반 인증 (Session-based Auth) - "서버의 장부"
전통적인 방식입니다. 서버가 메모리(또는 DB)에 **"누가 로그인했는지"**를 적어두고, 클라이언트에게는 그 장부의 **식별 번호(Session ID)**만 줍니다.
동작 순서
- 로그인: 사용자가 정보를 보냅니다.
- 생성: 서버는 JSESSIONID 같은 고유 ID를 생성하고, 서버 메모리(장부)에 {ID: "UserA", Role: "Admin"} 정보를 저장합니다.
- 발급: 클라이언트에게 Set-Cookie: JSESSIONID=123 헤더를 통해 ID를 줍니다.
- 인증: 클라이언트는 쿠키를 통해 ID를 보냅니다. 서버는 장부를 뒤져서 "아, 너 123번, UserA구나"라고 확인합니다.
코드 예시 (Spring Security)
Java
// 세션 방식은 별도 설정이 없으면 스프링 시큐리티의 기본값입니다.
// SecurityContextHolder가 세션(HttpSession)에 저장됩니다.
@GetMapping("/dashboard")
public String dashboard(HttpSession session) {
// 서버 메모리에 있는 세션 저장소에서 값을 꺼내옴
String user = (String) session.getAttribute("USER_ID");
if (user == null) {
return "redirect:/login";
}
return "Welcome " + user;
}
장단점
- 장점:
- 제어권(Control): 서버가 모든 정보를 쥐고 있습니다. 의심스러운 사용자가 있으면 서버에서 해당 세션을 지워버리면 즉시 강제 로그아웃(Kick out) 시킬 수 있습니다.
- 보안: 클라이언트는 의미 없는 ID 문자열만 가지고 있으므로, 정보가 탈취되어도(세션 하이재킹 제외) 개인정보 자체는 안전합니다.
- 단점:
- 확장성(Scalability): 서버가 1대면 괜찮지만, 10대로 늘어나면 문제입니다. A 서버에 로그인했는데 B 서버로 요청이 가면, B 서버는 장부에 없는 사람이라며 튕겨냅니다. (이를 해결하려면 Sticky Session이나 Redis Session Cluster가 필요함)
- 메모리 부하: 동시 접속자가 100만 명이면 서버 메모리가 터집니다.
2. 방식 2: 토큰 기반 인증 (Token-based Auth / JWT) - "디지털 서명된 출입증"
모던 웹/앱, MSA 환경의 표준입니다. 서버는 아무것도 기억하지 않습니다. 대신 사용자에게 위변조가 불가능한 **출입증(Access Token)**을 발급해 주고, "네가 알아서 들고 다녀"라고 합니다.
동작 순서
- 로그인: 사용자가 정보를 보냅니다.
- 발급: 서버는 {id: "UserA", role: "Admin"} 정보를 JSON으로 만들고, 서버만 아는 **비밀키로 서명(Signature)**하여 **JWT(JSON Web Token)**를 만들어 줍니다. (서버에는 저장 안 함!)
- 저장: 클라이언트는 이 토큰을 로컬 스토리지나 쿠키에 저장합니다.
- 인증: 요청할 때마다 헤더(Authorization: Bearer <Token>)에 토큰을 실어 보냅니다. 서버는 서명이 맞는지 수학적으로 검증만 합니다.
코드 예시 (JWT 파싱)
Java
public boolean validateToken(String token) {
try {
// DB 조회 없이, 서명(Signature)만 수학적으로 검증하면 끝
// 서버는 Stateless 상태를 유지할 수 있음
Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token);
return true;
} catch (JwtException e) {
return false; // 위변조되었거나 만료됨
}
}
장단점
- 장점:
- 확장성(Scalability): 서버가 상태를 저장하지 않으므로(Stateless), 서버를 무한대로 늘려도 상관없습니다. 트래픽 분산에 최적입니다.
- 멀티 디바이스: 모바일 앱, 웹 등 다양한 클라이언트에서 공통으로 쓰기 편합니다.
- 단점:
- 제어권 상실: 한 번 발급된 토큰은 유효기간이 끝날 때까지 서버가 강제로 회수할 수 없습니다. (도난당하면 끝)
- 트래픽 증가: 토큰 자체에 정보가 담겨 있어 세션 ID보다 길이가 깁니다. 매 요청마다 데이터 전송량이 늘어납니다.
3. 실무 비교: 언제 무엇을 쓰는가?
| 구분 | Session (Stateful) | JWT / Token (Stateless) |
| 저장 위치 | 서버 (메모리/DB) | 클라이언트 (헤더/스토리지) |
| 확장성 | 낮음 (Redis 등 별도 저장소 필요) | 높음 (서버 증설 용이) |
| 보안 제어 | 높음 (즉시 차단 가능) | 낮음 (만료될 때까지 차단 어려움) |
| 데이터 크기 | 작음 (ID만 전송) | 큼 (정보+서명 전송) |
| 추천 상황 | 관리자 페이지, 금융권, 동시 접속자 적은 PC 웹 | 대규모 B2C 서비스, 모바일 앱, 마이크로서비스(MSA) |
결론 및 하이브리드 전략
"보안이 최우선이고 접속자가 제한적이다"라면 세션 방식이 가장 안전하고 구현도 쉽습니다.
하지만 **"대규모 트래픽과 모바일 앱을 지원해야 한다"**면 JWT가 필수입니다. 단, JWT의 "제어권 상실" 문제를 보완하기 위해 실무에서는 Refresh Token을 도입합니다.
- Access Token (JWT): 유효기간 30분. (Stateless하게 검증)
- Refresh Token (UUID): 유효기간 2주. DB에 저장. (토큰 갱신 요청이 오면 DB를 확인)
이렇게 하면 평소에는 JWT의 빠른 성능을 누리고, 토큰 갱신 시점에는 DB를 체크하여 강제 로그아웃 기능을 구현할 수 있습니다.
'프로그래밍 > 디자인패턴' 카테고리의 다른 글
| 멀티테넌시 패턴(Multi-tenancy Pattern) 완벽 정리 (0) | 2025.12.07 |
|---|---|
| API 버저닝 패턴(API Versioning Pattern) 완벽 정리 (0) | 2025.12.07 |
| 샤딩 패턴(Sharding Pattern) 완벽 정리 (0) | 2025.12.07 |
| 리더 선출 패턴(Leader Election Pattern) 완벽 정리 (0) | 2025.12.07 |
| 비동기 요청-응답 패턴(Asynchronous Request-Reply) 완벽 정리 (0) | 2025.12.07 |