세션 vs 토큰, 알고 사용하기
회사 프로젝트에서 인증을 어떻게 사용하고 있는지 보다가, 이 기회에 세션이랑 토큰 개념을 확실히 정리해봤습니다.
“세션이랑 토큰 차이가 뭐야?” 하면 말문이 막혔거든요. 말을 잘 못하는 건지.. 이번에 확실히 잡고 갑시다.
인증 vs 인가, 뭐가 다른 건가요?
둘 다 비슷해 보이는데 완전 다른 개념입니다.
| 개념 | 질문 | 예시 |
|---|---|---|
| Authentication (인증) | “너 누구야?” | 로그인 |
| Authorization (인가) | “너 이거 해도 돼?” | 권한 체크 |
회사 건물로 비유하면 이렇습니다:
- 인증: 사원증 찍고 들어가는 것
- 인가: 서버실 출입 권한 있는지 확인하는 것
실무에서는 로그인이 인증이고, 로그인 후 특정 API나 페이지 접근 권한 체크가 인가입니다.
면접에서 이거 물어보면 건물 비유 써보기..? 저도 이제 그럴 예정이에요.
세션 기반 인증
HTTP는 원래 기억력이 없습니다
HTTP는 Stateless(무상태) 프로토콜입니다. 요청 끝나면 서버는 클라이언트가 누군지 까먹어요.
그래서 세션이 필요합니다. 서버가 클라이언트를 “기억”하기 위한 임시 저장소라고 보시면 됩니다.
동작 원리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[1] 로그인 요청
사용자 → 서버: "ID: hong, PW: 1234"
[2] 서버에서 세션 생성
서버: "인증 성공! 세션 만들어야지"
Session ID: abc123xyz
Session Data: {userId: "hong", loginTime: "..."}
→ 서버 메모리(또는 Redis)에 저장
[3] 세션 ID를 클라이언트에 전달
서버 → 사용자: Set-Cookie: JSESSIONID=abc123xyz
[4] 이후 요청마다 쿠키 자동 전송
사용자 → 서버: Cookie: JSESSIONID=abc123xyz
서버: "abc123xyz 세션 찾아보니까... 아 hong이구나!"
세션의 장단점
장점
- 서버가 세션 통제 가능합니다 (강제 로그아웃 쉬움)
- 구현 간단합니다 (HttpSession 쓰면 끝)
- 보안 정보가 서버에만 있습니다
단점
- 서버에 상태 저장 → 메모리 부담이 있습니다
- 서버 여러 대면 세션 공유 문제가 발생합니다
- Scale-out 할 때 복잡해집니다
서버 여러 대일 때 문제
1
2
3
4
사용자가 서버 A에서 로그인
→ 다음 요청이 로드밸런서에 의해 서버 B로 감
→ 서버 B는 세션 모름
→ "로그인 안 됐는데요?"
해결 방법은 이렇습니다:
- Sticky Session: 같은 사용자를 같은 서버로 보냅니다. 근데 서버 죽으면 끝이라 비추…
- Session Clustering: 서버끼리 세션 공유합니다. 네트워크 비용이 발생합니다.
- 외부 저장소: Redis 같은 곳에 세션 저장합니다. 많이 씁니다.
- 토큰 기반으로 전환: 서버가 상태를 안 가지게 합니다.
토큰 기반 인증
왜 등장했나요?
세션의 한계를 해결하기 위해 나왔습니다.
“서버가 상태를 저장해야 하네?” “그럼 서버가 상태를 안 가지면 되잖아!” “인증 정보를 클라이언트한테 줘버리자”
발상의 전환입니다.
세션 vs 토큰 핵심 차이
| 세션 기반 | 토큰 기반 |
|---|---|
| 서버가 “너 누구였지?” 기억해야 함 | 클라이언트가 “나 이 사람이야” 증명서 들고 다님 |
| 서버에 세션 저장 | 서버에 아무것도 저장 안 함 |
| Stateful | Stateless |
동작 원리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[1] 로그인 요청
사용자 → 서버: "ID: hong, PW: 1234"
[2] 서버에서 토큰 생성
서버: "인증 성공! 토큰 만들어야지"
토큰에 담을 정보: {userId: "hong", exp: "2시간 후"}
비밀키로 서명
→ 서버에 저장 안 함!
[3] 토큰을 클라이언트에 전달
서버 → 사용자: { "token": "eyJhbGc..." }
[4] 이후 요청마다 토큰 전송
사용자 → 서버: Authorization: Bearer eyJhbGc...
서버: "토큰 검증해보자... 서명 맞네, 만료 안 됐네, OK!"
→ DB나 Redis 조회 없이 바로 인증 완료
상세 비교
| 항목 | 세션 기반 | 토큰 기반 |
|---|---|---|
| 상태 저장 | 서버 (Stateful) | 클라이언트 (Stateless) |
| Scale-out | 세션 공유 필요 | 그냥 서버 늘리면 됩니다 |
| 인증 속도 | Redis/DB 조회 필요 | 서명 검증만 (빠름) |
| 강제 로그아웃 | 쉬움 (세션 삭제) | 어려움 (토큰 무효화 별도 처리) |
| 모바일 앱 | 쿠키 관리 복잡 | 토큰 저장 간편 |
빅테크가 토큰을 선호하는 이유
1. Scale-out이 쉽습니다
서버 100대 운영할 때를 생각해보면:
- 세션: Redis 클러스터 구축, 세션 동기화, 장애 대응… 머리 아픕니다.
- 토큰: 그냥 서버 늘리면 끝입니다. 각 서버가 독립적으로 토큰 검증합니다.
2. MSA 환경에 적합합니다
주문 서비스, 결제 서비스, 회원 서비스가 따로 있을 때:
- 세션: “회원 서비스에서 로그인했는데, 주문 서비스는 어떻게 알지?”
- 토큰: “토큰 들고 오면 각 서비스가 알아서 검증하면 됩니다”
3. 모바일 앱, SPA 대응에 좋습니다
React, Vue, 모바일 앱은 쿠키 관리가 번거롭습니다. 토큰을 헤더에 넣어서 보내는 게 훨씬 깔끔해요.
JWT (JSON Web Token)
토큰 기반 인증에서 가장 많이 쓰이는 형식입니다.
JWT 구조
점(.)으로 구분된 3파트로 구성됩니다.
1
2
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJob25nIiwiZXhwIjoxNzMzODAwMDAwfQ.X3qK9s2Fz8kR7vM1nB5pL2wY6tH4jD9gC0eA8uI3mZo
└────────── Header ──────────┘.└────────── Payload ──────────────────┘.└────────── Signature ──────────┘
| 파트 | 내용 |
|---|---|
| Header | 토큰 타입, 서명 알고리즘 |
| Payload | 실제 데이터 (사용자 정보, 만료시간 등) |
| Signature | 위변조 방지 서명 |
Header
1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}
주요 알고리즘:
- HS256: 대칭키 방식, 간단합니다
- RS256: 비대칭키 방식, MSA에 유리합니다
- ES256: 비대칭키, RSA보다 키가 짧습니다
Payload
1
2
3
4
5
6
{
"sub": "user123",
"name": "홍길동",
"iat": 1733700000,
"exp": 1733707200
}
주요 클레임:
sub: 토큰 대상 (보통 사용자 ID)exp: 만료 시간iat: 발급 시간
Signature
1
2
3
4
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
서버만 알고 있는 비밀키로 서명해서, 토큰이 위변조됐는지 검증할 수 있습니다.
⚠️ 중요: Base64는 암호화가 아닙니다
JWT의 Header와 Payload는 암호화가 아니라 인코딩입니다. 누구나 디코딩해서 볼 수 있어요.
→ 민감한 정보(비밀번호, 주민번호 등) 절대 넣으면 안됨!
직접 확인해보고 싶으시면: jwt.io
Access Token vs Refresh Token
| 구분 | Access Token | Refresh Token |
|---|---|---|
| 용도 | API 인증 | Access Token 재발급 |
| 만료 시간 | 짧음 (15분~1시간) | 김 (7일~30일) |
| 저장 위치 | 메모리 | httpOnly Cookie |
왜 두 개로 나눌까요?
Access Token만 있으면:
- 만료 짧게 → 자주 로그인 해야 합니다 (UX 별로)
- 만료 길게 → 탈취되면 오래 악용됩니다 (보안 별로)
두 개로 나누면:
- Access Token: 짧은 만료, 탈취돼도 금방 만료
- Refresh Token: 긴 만료, 안전하게 보관
- 보안과 UX 둘 다 챙길 수 있습니다!
JWT 보안 취약점 & 대응
| 취약점 | 대응 |
|---|---|
| XSS로 토큰 탈취 | httpOnly Cookie에 저장 |
| 네트워크 스니핑 | HTTPS 필수 |
| 강제 로그아웃 불가 | 토큰 블랙리스트 (Redis) |
| 알고리즘 조작 공격 | 서버에서 알고리즘 고정 |
실무에서 선택 기준
| 상황 | 추천 |
|---|---|
| 전통적인 웹사이트 (SSR) | 세션도 OK |
| 서버 1~2대, 간단한 서비스 | 세션도 OK |
| MSA, 대규모 트래픽 | 토큰 |
| 모바일 앱 + 웹 동시 지원 | 토큰 |
| SPA (React, Vue) | 토큰 |
레거시 시스템이라고 무조건 세션이 나쁜 건 아닙니다. 세션은 강제 로그아웃이 쉬워서 보안 이슈 발생 시 즉각 대응이 가능하다는 장점이 있어요.
상황에 맞게 선택하는 게 중요합니다.
정리
- 인증(Authentication): 너 누구야?
- 인가(Authorization): 너 이거 해도 돼?
- 세션: 서버가 상태 저장, 강제 로그아웃 쉬움, Scale-out 어려움
- 토큰: 클라이언트가 들고 다님, Scale-out 쉬움, 강제 로그아웃 어려움
- JWT: Header.Payload.Signature 구조, Payload는 암호화 아님 주의!
다음 편에서는 OAuth 2.0이랑 실무에서 쓰는 인증 패턴들 정리해볼게요!
이 글은 인증 시리즈 1편입니다.
- 1편: 세션 vs 토큰, 알고 사용하기 (현재 글)
- 2편: OAuth 2.0부터 HMAC까지 실무 인증 패턴 정리
- 3편: [try] 인증 실제로 적용해보기