JWT vs 세션 인증 비교: 무엇을 써야 할까? 저장 위치, 로그아웃, 보안까지 정리

JWT는 암호화가 아니라 서명이라서 누구나 내용을 읽을 수 있다. 세션과 JWT가 각각 어떻게 동작하는지, 강제 로그아웃이 어려운 이유, localStorage와 httpOnly 쿠키 중 어디에 저장해야 하는지, 리프레시 토큰까지 직접 토큰을 디코딩하고 변조해보며 정리했다.

"JWT가 더 최신이니까 JWT 쓰면 되죠?"

로그인을 구현할 때 가장 많이 나오는 질문이다. 결론부터 말하면 둘 중 무엇이 더 좋은 게 아니라, 상황에 따라 맞는 게 다르다. 그리고 생각보다 많은 서비스에는 오래된 세션 방식이 더 잘 맞는다.

둘의 차이는 한 문장으로 요약된다.

세션은 서버가 로그인 상태를 기억하고, JWT는 클라이언트가 로그인 증명서를 들고 다닌다.


세션 방식: 서버가 기억한다

  1. 로그인에 성공하면 서버가 세션을 만들고 저장소(메모리, Redis 등)에 { 세션 ID → 사용자 정보 }를 저장한다.
  2. 브라우저에는 세션 ID만 쿠키로 보낸다. (Set-Cookie: sid=a8f3...; HttpOnly)
  3. 이후 요청마다 브라우저가 쿠키를 자동으로 보내고, 서버는 세션 ID로 저장소를 조회해서 누구인지 확인한다.

세션 ID 자체는 아무 의미 없는 랜덤 문자열이다. 실제 정보는 전부 서버에 있다.

JWT 방식: 클라이언트가 증명서를 들고 다닌다

  1. 로그인에 성공하면 서버가 사용자 정보를 담은 토큰을 만들고 서명해서 돌려준다.
  2. 클라이언트는 토큰을 저장해두고, 요청마다 Authorization: Bearer <토큰> 헤더로 보낸다.
  3. 서버는 저장소를 조회하지 않고 서명만 검증해서, 위조되지 않았다면 토큰 안의 정보를 믿는다.

JWT(JSON Web Token)는 점(.)으로 구분된 세 부분으로 이루어져 있다.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9      ← 헤더 (알고리즘)
.eyJzdWIiOiI0MiIsIm5hbWUiOiJraW0iLC...   ← 페이로드 (사용자 정보)
.zaG-JogpvAo1fU4iPmB29IwMc6r3x07Ouug...  ← 서명

JWT는 암호화가 아니다: 직접 열어보기

여기서 가장 흔한 오해가 나온다. 헤더와 페이로드는 암호화된 게 아니라 Base64URL로 인코딩만 된 것이라서, 누구나 디코딩해서 읽을 수 있다. 아래 미리보기에서 직접 확인해보자.

이 데모에서 확인할 수 있는 두 가지가 JWT의 핵심이다.

  • 누구나 내용을 읽을 수 있다. 그래서 페이로드에 비밀번호, 주민번호, 내부 정보를 넣으면 안 된다.
  • 내용을 바꾸면 서명이 깨진다. role을 admin으로 바꿔도 서버만 아는 비밀 키 없이는 올바른 서명을 만들 수 없어서, 서버가 거부한다.

JWT의 서명은 "내용을 숨기는" 장치가 아니라 "내용이 바뀌지 않았음을 보증하는" 장치다.

서버에서 JWT를 검증할 때는 허용할 알고리즘을 명시하자(예: algorithms: ["HS256"]). 토큰 헤더의 alg 값을 그대로 믿으면, alg: "none"처럼 서명 없는 토큰을 받아들이는 취약점이 생길 수 있다. 직접 구현하지 말고 검증된 라이브러리를 쓰는 게 안전하다.


결정적인 차이: 강제 로그아웃

두 방식의 장단점은 대부분 "서버가 상태를 기억하느냐"에서 나온다.

비교세션JWT
로그인 상태를 기억하는 곳서버 저장소토큰 자체
요청마다 서버가 하는 일저장소 조회서명 검증 (조회 없음)
강제 로그아웃, 계정 정지세션 삭제로 즉시만료 전까지는 토큰이 계속 유효
서버 여러 대로 늘릴 때공유 저장소(Redis 등) 필요비밀 키만 공유하면 됨
다른 서비스, 모바일 앱과 공유쿠키 도메인 제약이 있음헤더로 보내면 어디서든 사용
데이터 크기쿠키에 짧은 ID만요청마다 수백 바이트 이상

JWT의 가장 큰 약점은 한 번 발급하면 되돌릴 수 없다는 점이다. 사용자가 비밀번호를 바꾸거나, 관리자가 계정을 정지해도, 이미 발급된 토큰은 만료 시간까지 유효하다. 이를 막으려면 "폐기된 토큰 목록"을 서버에 두고 매번 조회해야 하는데, 그러면 결국 세션처럼 서버가 상태를 기억하게 된다.

그래서 JWT를 쓸 때는 보통 액세스 토큰의 수명을 짧게(예: 15분) 하고, 리프레시 토큰으로 새 액세스 토큰을 발급받는 구조를 쓴다.

토큰수명저장 위치역할
액세스 토큰짧게 (5~15분)메모리 등API 요청에 사용
리프레시 토큰길게 (며칠~몇 주)httpOnly 쿠키액세스 토큰 재발급. 서버가 목록을 관리해서 폐기 가능

리프레시 토큰은 사용할 때마다 새 것으로 바꿔주고(Rotation), 이미 쓴 토큰이 다시 들어오면 탈취로 보고 전부 폐기하는 방식이 많이 쓰인다.


어디에 저장할까: localStorage vs httpOnly 쿠키

저장 위치XSS로 탈취CSRF 위험비고
localStorage가능 (자바스크립트로 읽힘)없음 (자동 전송 안 됨)페이지에 악성 스크립트가 하나라도 실행되면 토큰이 털린다
httpOnly 쿠키불가 (자바스크립트로 못 읽음)있음 (자동 전송됨)SameSite 속성으로 크게 줄일 수 있음

XSS(Cross-Site Scripting)는 공격자의 스크립트가 내 페이지에서 실행되는 공격이고, CSRF(Cross-Site Request Forgery)는 다른 사이트가 사용자의 쿠키를 이용해 내 서버로 요청을 보내게 만드는 공격이다.

인증 정보를 쿠키에 담을 때는 이 속성들을 함께 쓴다.

Set-Cookie: sid=a8f3...; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=86400
속성효과
HttpOnly자바스크립트(document.cookie)로 읽을 수 없음 → XSS로 탈취 방지
SecureHTTPS에서만 전송
SameSite=Lax다른 사이트에서 시작된 요청에는 (링크 이동 같은 경우를 빼고) 쿠키를 보내지 않음 → CSRF 대부분 차단

httpOnly 쿠키가 XSS를 완전히 막아주는 건 아니다. 악성 스크립트가 토큰을 훔쳐 가지는 못하지만, 그 페이지 안에서 사용자 대신 요청을 보낼 수는 있다. 저장 위치와 상관없이 XSS 자체를 막는 것(입력값 이스케이프, CSP)이 기본이다.


그래서 무엇을 쓸까

상황추천
일반적인 웹 서비스 (프론트와 API가 같은 도메인 계열)세션 + httpOnly 쿠키. 단순하고 강제 로그아웃이 쉽다
모바일 앱, 여러 서비스가 같은 인증을 공유JWT (짧은 액세스 토큰 + 리프레시 토큰)
서버 간 통신, 마이크로서비스 내부 인증JWT. 저장소 조회 없이 검증할 수 있다
즉시 차단이 중요한 서비스 (금융, 관리자 도구)세션. 또는 JWT라도 폐기 목록 운영

정리: 왜 알아두면 좋은가

오해사실
"JWT는 암호화돼서 안전하다"서명만 됐을 뿐, 누구나 내용을 읽는다
"JWT가 세션보다 최신이라 더 좋다"강제 로그아웃이 어렵다는 분명한 약점이 있다
"토큰은 localStorage에 넣으면 된다"XSS 한 번에 탈취된다. httpOnly 쿠키를 고려하자
"쿠키는 CSRF 때문에 위험하다"SameSite, CSRF 토큰으로 충분히 막을 수 있다

인증 방식을 고를 때는 유행보다 "로그인 상태를 누가 기억해야 하는가", 그리고 "계정을 즉시 차단해야 할 일이 있는가" 를 먼저 생각해보자. 그 답이 대부분의 선택을 정해준다.