본문으로 건너뛰기

Cookie, Session, Token(JWT) 정리하기

2023년 10월 13일에 작성되었고, 블로그를 이전하며 옮겨온 포스트입니다.
여기에서 원본을 확인할 수 있습니다.

프로젝트를 진행하면서 백엔드(Spring Boot)에서는 유효 기간이 존재하는 접근 토큰을 발급하고, React에서는 그 토큰을 받아 클라이언트의 localStorage에 저장하여 특정 행위를 할 때 사용자임을 인증하는 방식으로 JWT 방법을 사용했었는데, 사용을 하기만 할 뿐 개념이나 쿠키, 세션 방식과의 차이점은 정확히 이해하지 못 했다. 이 글에서는 사용자 인증 방식 중 쿠키, 세션, 토큰 방식의 차이점에 대해 이해하고 공부하면서 정리한 것들을 적어보려 한다.

HTTP의 특성

HTTP: 인터넷 상에서 데이터를 주고 받기 위해 서버 또는 클라이언트 모델을 따르는 프로토콜

HTTP는 위 사진과 같이 서버로 데이터 요청을 하자마자 연결을 끊어버리기 때문에 상태 정보나 통신 상태 같은 정보가 클라이언트에게 남아있지 않다. HTTP는 그림과 아래 내용과 같이 2가지 특성을 가지고 있다.

  1. 비연결성
  2. 무상태성

비연결성의 장점은 서버의 불필요한 자원 낭비를 줄일 수 있다는 것이다. 트래픽이 계속해서 발생되는 것이 아닌 일회성 요청으로 끝나기 때문이다. 이와 같은 장점 반면에, 일회성으로 요청하고 끝나기 때문에 클라이언트를 식별할 수 없다는 것이다. 그래서 웬만한 웹 페이지에서는 로그인과 회원가입 기능이 필수적으로 존재하는데, 비연결성 때문에 클라이언트 식별이 불가능해져 사용자 인증이 불가능한 것이다.

이와 같은 단점(?)을 해결하기 위해 우리는 Session(세션)과 Cookie(쿠키)를 사용한다.

Session(세션)

세션 인증

세션은 사용자의 인증 정보를 Cookie에 저장하는 것이 아닌, 서버에 저장하고 관리한다. 대략적인 세션의 흐름을 보면 아래 이미지와 같다.

장점

  1. 클라이언트의 웹 브라우저에 의존하지 않아도 됨.
  2. 요청이 외부에 노출되더라도, 민감 정보가 포함되지 않음.
  3. 요청이 들어올 때마다 사용자 정보를 확인하지 않아도 됨.

단점

  1. 해커가 중간에 sessionId를 탈취하여 위장할 수 있음.
  2. 세션을 생성해 메모리에 저장하기 때문에 사용자가 많아지면 서버에 부하가 생길 수 있음.

Cookie(쿠키)

쿠키 인증

쿠키는 웹 서버가 생성하여 웹 브라우저로 전송하는 작은 정보 파일이다. 웹 브라우저는 수신한 쿠키를 미리 정해진 기간 동안 또는 웹 사이트에서의 사용자 세션 기간 동안 저장한다. 클라이언트에서 서버에 요청을 보낼 때 쿠키의 정보를 헤더에 함께 추가하여 서버에서는 쿠키의 정보로 사용자를 구분할 수 있다.

장점

별도 인증 과정을 거치지 않고 서버에서 사용자를 구별할 수 있다.

단점

  1. 보안에 매우 취약하다. 요청 시 쿠키에 포함된 민감 정보를 보내기 때문에, 중간에 해커가 탈취하게 될 경우 유출될 위험이 크다.
  2. 브라우저 마다 쿠키에 대해 지원 형태가 다르다.
  3. 쿠키에 저장된 정보가 많아질 수록, 부하가 심해진다.
  4. 저장할 수 있는 쿠키 파일의 크기가 한정되어 있다.

JWT(JSON Web Token)

토큰 기반 인증 JWT(JSON Web Token)

JWT(JSON Web Token)은 사용자 인증에 필요한 정보를 암호화 시킨 토큰이다. 이 방식도 Session과 Cookie 방식과 동일하게 헤더에 Token을 포함해 요청을 보낸다. JWT는 Header, Payload, Signature 3가지를 .으로 구분해 조합하여 생성된다.

Header

Header 부분에는 아래 2가지 내용이 포함된다.

  1. 서명 알고리즘(SHA256, RSA)
  2. 사용된 토큰의 타입
{
    "alg": "SHA256",
    "typ": "JWT"
}

Payload

Payload 부분에는 실제로 토큰에 담길 사용자의 정보가 담겨있다. 주로 클라이언트의 아이디와 유효 기간 등이 담겨있다. 아래 정보는 Base64Url로 인코딩된다.

{
    "sub": "1234567890",
    "name": "John Doe",
    "admin": true
}

Signature

Signature은 인코딩된 Header와 Payload를 합친 뒤, 비밀 키로 암호화하여 생성된다. Header와 Payload는 단순히 인코딩 된 값에 불과하기에 해커가 정보를 빼나갈 수 있지만, Signature의 경우에는 서버에서 관리하고 제공하는 비밀 키가 없으면 복호화가 불가능하다. 그래서 Signature은 토큰의 위변조 여부를 확인한다.

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret123)

JWT 인증 과정

  1. 클라이언트에서 로그인 요청이 들어오면, 서버에서는 로그인 요청이 유효한지 확인 후 클라이언트 고유 ID 등의 정보를 Payload에 저장한다.
  2. 암호화 할 Secret Key를 사용하여 암호화 한 뒤 클라이언트에게 JWT(JSON Web Token)를 제공한다.
  3. 클라이언트 측에서는 발급 받은 토큰을 API 요청을 할 때마다 Header(Authorization)에 포함시켜 요청한다.
  4. 서버는 반환 값을 제공하기 전, 전달 받은 토큰을 Secret Key로 복호화하여 토큰이 유효한지 먼저 확인한다.
  5. 토큰이 유효하다면, 요청에 응답한다.

장점

  1. Header와 Payload를 통해 Signature을 생성하기 떄문에 토큰의 위변조 위험도가 적다.
  2. Session 방식과 다르게 별도의 저장 공간이나 메모리가 필요하지 않다.
  3. 확장성이 좋다.

단점

  1. Session 방식과 Cookie 방식과 달리 토큰의 길이가 길기 때문에 많은 요청이 발생할 경우 네트워크 부하가 심해질 수 있다.
  2. 토큰이 만료되기 이전에는 특정 사용자의 접속을 강제로 차단할 수 없다. 그렇다고 토큰의 유효 기간을 짧게 설정하면 재로그인이 필요한 주기가 짧아져 사용자의 불편을 야기할 수 있다.

Refresh Token

유효 기간이 비교적 짧은 Access Token의 불편함 보완을 위해 Refresh Token을 사용한다. Refresh Token은 Access Token의 재발급을 도와주기 때문에 유효 기간이 더 길다. (대략 1-2주)

즉, Access Token은 사용자 인증 과정에 실질적으로 사용되는 토큰이고 Refresh Token은 이 Access Token의 재발급을 도와주기 때문에 사용자가 잦은 재로그인을 경험하지 않도록 해준다.

Refresh Token을 관리하는 방법에는 여러가지가 있지만, 데이터베이스에 Refresh Token 값을 저장하고, 비교하여 유효성이 검증된다면 Access Token을 발급한다. 만일 Refresh Token 까지도 만료되었다면, 어쩔 수 없이 재로그인이 필요하다.

JWT의 장점 부분에서 Session과 다르게 별도의 저장 공간이나 메모리가 필요하지 않다고 언급하였는데, Refresh Token을 사용하게 되면 어쩔 수 없이 추가 공간이 필요해진다.

마무리

최근 Cookie나 Session 방식 보다 토큰 인증 방식으로 JWT를 많이 이용하고 있다. "반드시 JWT를 써야해!"는 아니지만, 각각의 서비스에 맞는 방식을 선택하여 사용하면 좋을 것 같다.