우아한테크코스 테코톡
클라우디의 세션과 JWT
https://youtu.be/AGZA59MjhCg?si=NsttBiBb4l1GumJg
클라우디의 세션과 JWT
- 클라우디의 세션과 JWT
- 세션과 JWT: 로그인 방식은 어떻게 선택해야 할까?
- HTTP의 무상태성이란 무엇인가
- 매 요청마다 아이디와 비밀번호를 보내면 안 될까?
- 세션 방식 로그인
- 세션 방식의 장점
- 세션 방식의 단점
- JWT 방식 로그인
- JWT의 구조
- Header
- Payload
- Signature
- JWT 방식의 장점
- JWT 방식의 단점
- 세션은 어디에 보관할까?
- 쿠키의 약점: CSRF
- 쿠키 보안 옵션
- HttpOnly
- Secure
- SameSite
- JWT는 어디에 보관할까?
- Local Storage에 저장하는 경우
- Cookie에 저장하는 경우
- 세션 vs JWT 비교
- Access Token & Refresh Token 전략
- 동작 흐름
- 이 전략이 보완하는 문제
- JWT 탈취 문제 완화
- 세션의 DB 부담 완화
- 이 전략도 완벽하지는 않다
- 어떤 방식을 선택해야 할까?
- 세션이 적합한 경우
- JWT가 적합한 경우
- 현실적인 선택
- 마무리
세션과 JWT: 로그인 방식은 어떻게 선택해야 할까?
웹 서비스에서 로그인은 거의 필수 기능이다. 그런데 HTTP는 기본적으로 무상태성(stateless) 을 가진다. 즉, 서버는 클라이언트가 이전에 어떤 요청을 보냈는지 기억하지 않는다. 그래서 로그인 기능을 구현하려면 “서버가 사용자를 어떻게 기억할 것인가?”라는 문제를 해결해야 한다.
대표적인 방식이 세션(Session) 과 JWT(JSON Web Token) 다.
HTTP의 무상태성이란 무엇인가
HTTP는 요청과 응답을 주고받는 구조다.
클라이언트 → 요청
서버 → 응답
그런데 서버는 이전 요청을 기억하지 않는다.
예를 들어 카페에서 손님이 “아메리카노 한 잔 주세요”라고 주문한 뒤, 바로 이어서 “한 잔 더 주세요”라고 말했는데 점원이 이전 주문을 기억하지 못하고 “방금 뭘 주문하셨죠?”라고 되묻는 상황과 같다.
웹 서버도 마찬가지다.
첫 번째 요청: 로그인
두 번째 요청: 마이페이지 조회
서버는 두 번째 요청만 보면 이 사용자가 방금 로그인한 사용자인지 알 수 없다.
그래서 로그인 상태를 유지하기 위한 별도 방식이 필요하다.
매 요청마다 아이디와 비밀번호를 보내면 안 될까?
가장 단순한 방법은 매 요청마다 아이디와 비밀번호를 보내는 것이다.
마이페이지 요청 + 아이디 + 비밀번호
게시글 작성 요청 + 아이디 + 비밀번호
결제 요청 + 아이디 + 비밀번호
하지만 이 방식은 매우 위험하다.
요청이 탈취되면 비밀번호가 그대로 노출된다. 그리고 많은 사용자는 여러 서비스에서 같은 비밀번호를 재사용하기 때문에, 하나의 서비스에서 탈취된 비밀번호가 다른 서비스 침해로 이어질 수 있다.
그래서 등장한 방식이 세션이다.
세션 방식 로그인
세션 방식은 서버가 사용자의 로그인 상태를 저장하는 방식이다.
동작 흐름은 다음과 같다.
1. 사용자가 아이디와 비밀번호로 로그인한다
2. 서버가 사용자 정보를 확인한다
3. 서버가 세션을 생성하고 저장한다
4. 서버가 세션 ID를 클라이언트에게 쿠키로 내려준다
5. 클라이언트는 이후 요청마다 세션 ID 쿠키를 함께 보낸다
6. 서버는 세션 ID를 조회해 사용자를 식별한다
즉, 세션 방식은 사용자가 매번 비밀번호를 보내지 않아도 된다. 대신 서버가 세션 저장소에서 사용자를 확인한다.
세션 방식의 장점
세션 방식의 가장 큰 장점은 서버가 로그인 상태를 통제할 수 있다는 점이다.
예를 들어 사용자가 로그아웃하면 서버에 저장된 세션을 삭제하면 된다.
세션 삭제 = 로그인 무효화
또한 의심스러운 세션을 강제로 만료시키거나, 관리자 판단에 따라 특정 사용자의 세션을 제거할 수도 있다.
보안 관점에서는 서버가 상태를 관리한다는 점이 장점이 된다.
세션 방식의 단점
세션 방식의 단점은 서버 부담이다.
매 요청마다 서버는 세션 ID를 확인하고, 세션 저장소에서 사용자 정보를 찾아야 한다.
요청
→ 세션 ID 확인
→ DB 또는 Redis 조회
→ 사용자 확인
→ 응답
사용자가 많아질수록 서버와 세션 저장소의 부하가 증가한다.
또한 서버가 여러 대로 늘어나면 세션 관리가 복잡해진다.
서버 A에는 세션 있음
서버 B에는 세션 없음
이 문제를 해결하려면 세션 클러스터링, Sticky Session, Redis 같은 중앙 세션 저장소가 필요하다.
JWT 방식 로그인
JWT는 JSON Web Token의 약자다. 인증에 필요한 정보를 토큰 자체에 담는 방식이다.
세션 방식이 “번호표를 주고 서버가 명부를 확인하는 방식”이라면, JWT는 “신분증을 발급하고 요청마다 신분증을 확인하는 방식”에 가깝다.
JWT는 점(.)을 기준으로 세 부분으로 구성된다.
Header.Payload.Signature
JWT의 구조
Header
토큰 타입과 서명 알고리즘 정보를 담는다.
{
"typ": "JWT",
"alg": "HS256"
}
Payload
사용자 정보나 권한 같은 실제 데이터를 담는다.
{
"userId": 1,
"role": "USER"
}
단, Payload는 암호화된 영역이 아니다. Base64로 인코딩된 값이므로 누구나 디코딩해서 볼 수 있다. 따라서 비밀번호나 민감한 개인정보를 넣으면 안 된다.
Signature
토큰이 위조되지 않았는지 검증하기 위한 값이다.
서버는 Header와 Payload를 서버의 비밀 키로 서명하고, 요청이 들어올 때 다시 검증한다.
서명이 일치한다 = 서버가 발급한 토큰이다
서명이 다르다 = 위조된 토큰이다
JWT 방식의 장점
JWT의 가장 큰 장점은 서버가 매 요청마다 DB를 조회하지 않아도 된다는 점이다.
요청
→ JWT 확인
→ 서명 검증
→ 사용자 식별
세션 저장소를 조회하지 않고도 토큰 자체로 사용자를 확인할 수 있다.
그래서 서버가 여러 대로 확장되는 환경에서 유리하다.
서버 A도 검증 가능
서버 B도 검증 가능
서버 C도 검증 가능
비밀 키만 공유하고 있으면 어떤 서버든 토큰을 검증할 수 있다.
JWT 방식의 단점
JWT의 가장 큰 단점은 토큰 탈취 시 무효화가 어렵다는 점이다.
세션은 서버 저장소에서 삭제하면 끝난다.
세션 삭제 → 즉시 무효화
하지만 JWT는 서버가 토큰을 저장하지 않는 구조이기 때문에, 이미 발급된 토큰을 강제로 막기 어렵다.
즉, 탈취된 JWT가 아직 만료되지 않았다면 공격자가 계속 사용할 수 있다.
토큰 탈취
→ 만료 전까지 사용 가능
이 점이 JWT의 가장 큰 보안상 약점이다.
세션은 어디에 보관할까?
세션 방식에서는 보통 세션 ID를 쿠키에 저장한다.
쿠키는 브라우저가 요청마다 자동으로 서버에 포함시켜 보낸다.
Cookie: JSESSIONID=...
이 덕분에 프론트엔드 코드에서 직접 세션 ID를 읽어 요청에 넣지 않아도 된다.
또한 HttpOnly 옵션을 사용하면 JavaScript에서 쿠키에 접근할 수 없게 만들 수 있어 XSS로 인한 탈취 위험을 줄일 수 있다.
쿠키의 약점: CSRF
쿠키는 자동으로 요청에 포함된다는 장점이 있지만, 이 특성 때문에 CSRF 공격에 취약할 수 있다.
예를 들어 사용자가 은행 사이트에 로그인한 상태에서 악성 사이트에 접속했다고 가정하자.
악성 사이트가 은행 서버로 요청을 보내면, 브라우저는 은행 쿠키를 자동으로 함께 보낼 수 있다.
서버는 이를 정상 사용자의 요청으로 오해할 수 있다.
쿠키 보안 옵션
쿠키를 사용할 때는 다음 옵션이 중요하다.
HttpOnly
Secure
SameSite
Domain
Path
HttpOnly
JavaScript에서 쿠키를 읽지 못하게 한다.
XSS로 쿠키 탈취 방지
Secure
HTTPS 환경에서만 쿠키를 전송한다.
중간자 공격 위험 감소
SameSite
다른 사이트에서 요청할 때 쿠키를 보낼지 결정한다.
Strict
Lax
None
CSRF를 줄이기 위해 보통 Lax 또는 Strict를 고려한다.
JWT는 어디에 보관할까?
JWT는 보통 두 가지 저장소를 고민한다.
Local Storage
Cookie
Local Storage에 저장하는 경우
장점은 사용하기 쉽다는 것이다.
JavaScript에서 쉽게 읽을 수 있다.
localStorage.getItem("accessToken");
그래서 SPA 환경에서 Authorization 헤더에 직접 넣기 편하다.
Authorization: Bearer token
하지만 단점이 크다.
XSS 공격으로 악성 스크립트가 주입되면 Local Storage의 토큰을 그대로 읽어갈 수 있다.
Cookie에 저장하는 경우
쿠키에 JWT를 저장하면 HttpOnly 옵션을 사용할 수 있다.
그러면 JavaScript가 토큰을 직접 읽을 수 없다.
악성 스크립트가 주입되어도 토큰 읽기 어려움
다만 쿠키는 자동으로 요청에 포함되므로 CSRF 대응이 필요하다.
따라서 쿠키에 저장한다면 다음 조합을 함께 고려해야 한다.
HttpOnly
Secure
SameSite
CSRF Token
발표에서도 두 방식 모두 완벽하지는 않지만, XSS 공격을 막을 수 있다는 점에서 일반적으로 쿠키 보관이 권장된다고 설명한다.
세션 vs JWT 비교
| 구분 | 세션 | JWT |
|---|---|---|
| 상태 관리 | 서버가 관리 | 토큰 자체에 정보 포함 |
| 서버 저장소 | 필요 | 기본적으로 불필요 |
| 확장성 | 상대적으로 복잡 | 상대적으로 유리 |
| 강제 로그아웃 | 쉬움 | 어려움 |
| 탈취 대응 | 세션 삭제 가능 | 만료 전까지 위험 |
| 요청 처리 비용 | 세션 조회 필요 | 서명 검증 중심 |
| 대표 보관 위치 | 쿠키 | 쿠키 또는 Local Storage |
Access Token & Refresh Token 전략
세션과 JWT는 각각 장단점이 뚜렷하다.
세션은 서버 통제가 쉽지만 서버 부담이 크다. JWT는 확장성이 좋지만 탈취 시 무효화가 어렵다.
이 두 방식의 단점을 줄이기 위해 자주 사용하는 전략이 Access Token & Refresh Token 전략이다.
핵심은 토큰을 두 개로 나누는 것이다.
Access Token: 짧은 유효기간, API 요청에 사용
Refresh Token: 긴 유효기간, Access Token 재발급에 사용
동작 흐름
로그인 시 서버는 두 토큰을 발급한다.
Access Token
Refresh Token
이때 Refresh Token은 서버 DB 또는 Redis에 저장한다.
이후 클라이언트는 Access Token으로 API를 호출한다.
API 요청 → Access Token 검증 → 응답
Access Token이 만료되면 Refresh Token으로 새 Access Token을 발급받는다.
Access Token 만료
→ Refresh Token 제출
→ 서버가 Refresh Token 확인
→ 새 Access Token 발급
Refresh Token까지 만료되었거나 무효화되었다면 다시 로그인해야 한다.
이 전략이 보완하는 문제
JWT 탈취 문제 완화
Access Token의 유효 기간을 짧게 가져가면, 탈취되더라도 사용할 수 있는 시간이 제한된다.
Access Token 탈취
→ 짧은 시간 후 만료
Refresh Token은 서버에 저장하므로 탈취가 의심되면 무효화할 수 있다.
Refresh Token 삭제
→ 재발급 차단
세션의 DB 부담 완화
일반 API 요청에서는 Access Token만 검증하면 된다.
DB 조회 없이 서명 검증
Refresh Token은 Access Token 재발급 시에만 사용되므로, 매 요청마다 DB를 조회하는 세션 방식보다 부담을 줄일 수 있다.
이 전략도 완벽하지는 않다
Access Token이 만료되기 전에 탈취되면 막기 어렵다.
또한 Refresh Token이 탈취되면 공격자가 계속 Access Token을 재발급받을 수 있다.
그래서 Refresh Token은 더 강하게 보호해야 한다.
실무에서는 다음 전략을 함께 사용한다.
Refresh Token Rotation
Refresh Token 재사용 감지
기기별 토큰 관리
로그아웃 시 Refresh Token 삭제
HttpOnly Secure Cookie 저장
어떤 방식을 선택해야 할까?
정답은 서비스 특성에 따라 다르다.
세션이 적합한 경우
서버 통제가 중요하다
강제 로그아웃이 자주 필요하다
관리자 페이지나 내부 시스템이다
사용자 규모가 크지 않다
JWT가 적합한 경우
서버 확장성이 중요하다
여러 서버에서 인증을 공유해야 한다
모바일 앱, SPA, API 중심 서비스다
상태 없는 인증 구조가 필요하다
현실적인 선택
대부분의 현대 서비스에서는 다음 구성을 많이 사용한다.
짧은 만료 시간의 Access Token
+
서버에 저장되는 Refresh Token
+
HttpOnly Secure Cookie
이 방식은 JWT의 확장성을 가져가면서도, Refresh Token을 통해 어느 정도 서버 통제력을 확보할 수 있다.
마무리
HTTP는 무상태성이 있기 때문에 로그인 상태를 유지하려면 별도의 인증 유지 전략이 필요하다.
세션은 서버가 상태를 관리하므로 보안 통제가 쉽지만, 사용자가 많아질수록 서버와 세션 저장소의 부담이 커진다.
JWT는 토큰 자체에 인증 정보를 담기 때문에 확장성은 좋지만, 토큰 탈취 시 만료 전까지 막기 어렵다는 단점이 있다.
그래서 실무에서는 두 방식의 장점을 절충한 Access Token & Refresh Token 전략을 자주 사용한다.
정리하면 다음과 같다.
세션 = 서버가 기억한다
JWT = 클라이언트가 증명서를 들고 다닌다
Access Token + Refresh Token = 짧게 쓰고, 길게 관리한다
로그인 방식 선택에서 중요한 것은 “무엇이 더 최신인가”가 아니라, 서비스의 보안 요구사항, 확장성, 운영 복잡도, 사용자 경험을 함께 고려하는 것이다.