인증 방법 비교하기 : 쿠키, 세션, JWT, PASETO

섭승이·2025년 4월 27일

핵심 요약(빠르게 비교하기)

쿠키세션JWTPASETO
정의클라이언트에 저장되는 작은 데이터 조각서버에 저장되는 사용자 상태 정보서명된 JSON 토큰개선된 보안 표준을 적용한 토큰
저장 위치클라이언트(브라우저)서버클라이언트클라이언트
정보 저장 방식key-value 형식의 데이터세션 ID를 통한 서버 조회토큰에 사용자 정보, 권한 등을 포함토큰에 사용자 정보, 권한 등을 포함
보안성위험좋음암호화로 위조 방지JWT보다 보안성 강화
서버 부담낮음높음낮음낮음
스케일링서버가 세션을 관리해야 하므로 어려움세션 서버 확장 시 복잡성 증가서버 확장 쉬움서버 확장 쉬움
단점변조 위험, 용량 제한서버 부하 올라감, 확장 어려움서명만 있는 경우 노출 위험성커뮤니티, 지원 라이브러리 적음
구현 복잡도쉬움쉬움복잡함JWT보단 쉽지만, 세션, 쿠키보단 어려움


인증이 무엇일까? 왜 필요한 것일까?

인증은 권한이 있는 사용자만 서비스 내의 특정 데이터 또는 작업에 접근할 수 있도록 보장해주는 역할을 합니다.

인증이 없다면 민감한 정보에 무단으로 접근하고, 정보가 유출되고, 악의적인 공격에 취약해져 심각한 재정적 손실, 평판 손상, 개인정보 침해와 같은 문제로 이어지게 됩니다.

"그렇다면 인증 절차가 왜 필요하게 된걸까요? 그냥 서버가 사용자가 누구인지 기억하게 만들면 안될까요??"

HTTP 통신은 2가지의 큰 특성을 가지고 있습니다.

1. Stateless

  • 상태가 유지되지 않는다. 즉 요청 → 응답이 종료되면 서버는 클라이언트의 이전 상태를 보존하지 않습니다.
  • 상태를 유지하는 stateful의 경우 항상 같은 서버가 유지되어야 합니다. 하지만 stateless의 경우 클라이언트의 요청을 어느 서버가 응답해도 상관이 없기 때문에 대용량 트래픽 처리에 용이합니다.

  1. Connectionless
  • 연결이 유지되지 않는다. 즉 요청 → 응답이 완료되면 서버는 바로 TCP/IP 연결을 끊어버립니다. 따라서 요청을 보내려고 할 때마다 연결 준비 단계인 3-way handshake를 진행합니다.
  • Connectionless 한 특성을 통해 서버의 자원을 효율적으로 관리하고, 수 많은 클라이언트의 요청에도 대응할 수 있게 됩니다. (다수의 연결을 유지하려면 수많은 리소스가 발생)

따라서 HTTP 통신 과정에서는 요청 - 응답 과정에서 사용자가 누군지 인증하는 절차를 진행해야 합니다.

이때 "누가" 로그인 중인지에 대한 정보를 인증하기 위해 아래와 같은 방법을 사용합니다.

  1. 쿠키
  2. 세션
  3. JWT 토큰
  4. PASETO

하나하나 살펴보며 각각의 개념, 동작 방식, 장단점을 정리해보겠습니다.


쿠키

서버가 클라이언트 브라우저에 저장하는 작은 데이터 조각입니다. 즉 사용자가 갖고 있는 정보입니다.

1. 로그인을 할 때 사용자는 본인의 정보(사용자 이름, 비밀 번호 등)를 제공합니다.
2. 서버가 요청을 받으면 유효성을 검증하고(인증), 응답 헤더에 set-cookie : 를 통해 쿠키를 추가하여 응답합니다.
3. 서버에게 받은 쿠키는 사용자가 가지고 있다가(로컬 PC에 저장) 다시 서버에 요청할 때 쿠키와 함께 요청합니다.
4. 동일 사이트에 재방문할 때 클라이언트의 PC에 쿠키가 있는 경우 지속적으로 쿠키와 함께 요청을 보내고 서버는 응답할때 쿠키를 심어서 보내줍니다.

사진 출처

쿠키 사용의 장점

  1. 모든 주요 브라우저에서 기본적으로 지원됩니다.
  2. 반 영구적으로 저장되며 특정 시간 이후 만료되도록 설정하거나, 사용자가 수동으로 지울 때까지 유지됩니다.
  3. 개발자가 쿠키 전송을 처리하기 위해 추가 코드를 작성할 필요 없이, 브라우저가 자동으로 쿠기를 넣어줍니다.

쿠키 사용의 문제점

  1. XSS : 악성 스크립트가 브라우저에 삽입되면 쿠키에 접근이 가능해집니다. (HttpOnly 플래그 설정 필요!!)
  2. Man-in-the-Middle(MITM) Attack : 쿠키가 암호화되지 않은 HTTP를 통해 전송될 경우 쿠키 탈취가 가능해집니다. (HTTPS 요청을 통해서만 전송하는게 필요!!)
  3. CSRF : 요청에 자동으로 포함되기 때문에 사용자의 의지와 상관 없이 특정 요청을 보내도록 유도할 수 있게됩니다. ( CSRF 토큰 구현, samesite 속성 사용!!)
  4. 작은 저장 용량 : 일반적으로 사이트 당 20개, 모두 합쳐서 300개가 최대로 개수 제한이 존재합니다. 또한 각 쿠키는 4Byte를 넘을 수 없습니다. (필수 데이터만 저장!!)
  5. Cookie Overflow : 쿠키의 크기가 크거나 너무 많으면 성능 문제나 요청 거부로 이어질 수 있습니다. (만료 시간 적용!!)


세션

사용자가 브라우저에 있는 동안 같은 사용자로부터 들어오는 요청들을 하나의 상태로 보고 그 상태를 유지시키는 기술입니다.

즉 브라우저가 종료되기 전까지 클라이언트의 요청을 유지하게 해줍니다.

1. 로그인을 할 때 사용자는 본인의 정보(사용자 이름, 비밀 번호 등)를 제공합니다.
2. 서버가 요청을 받으면 유효성을 검증하고(인증), 사용자를 위한 세션을 생성하고 세션 ID를 응답 헤더의 쿠키에 추가하여 사용자에게 보내줍니다.(응답)
3. 서버는 세션 데이터를 메모리, 데이터베이스, 다른 저장 시스템에 저장합니다.
4. 사용자는 받은 세션 ID를 브라우저 쿠키에 저장합니다.
5. 이후 사용자가 요청할 때마다 세션 ID를 자동으로 요청 헤더에 추가하여 요청합니다.
6. 서버에서는 요청 헤더의 세션 ID 값을 저장소에서 찾아보고 유효한지 확인한 후 요청을 처리하고 응답합니다.

사진 출처

세션 사용의 장점

  1. 세션 데이터는 서버에 저장되므로 XSS와 같은 공격에 노출될 위험이 줄어듭니다.
  2. 세션은 서버에서 무효화하여 사용자 접근을 즉시 종료할 수 있습니다.
  3. 세션은 서버에 저장되므로 클라이언트의 웹 브라우저에 의존하지 않아도 됩니다.
  4. 세션 ID만 보내므로, 세션의 크기가 커도 네트워크 부하가 거의 없습니다.

세션 사용의 문제점

  1. Storage Overhead : 활성 세션 수가 많으면 상당한 서버 리소스가 소모됩니다. 즉 서버에 부하가 많이 가게 됩니다. (효율적인 세션 스토리지를 사용하면 어느 정도 완화 가능)
  2. Session Hijacking : 공격자가 세션 ID를 훔치면 사용자를 사칭할 수 있게 됩니다. (HTTPS 를 사용하여 암호화하고, 세션 시간 초과를 구현하면 어느 정도 완화 가능)
  3. Scalability(확장성) : 분산 시스템에서는 세션 데이터를 여러 서버간에 공유해야 하므로, 중앙 집중식 데이터베이스나 sticky session와 같은 추가 인프라 작업이 필요합니다. 따라서 복잡성이 증가하고, 잠재적인 병목 현상이 발생할 수 있습니다.

JWT

JWT는 json 객체에 인증에 필요한 정보들을 담은 후 비밀키로 서명한 토큰 인증 방식으로, Base 64로 인코딩되어 있습니다.


1. 로그인을 할 때 사용자는 본인의 정보(사용자 이름, 비밀 번호 등)를 제공합니다.
2. 서버가 요청을 받으면 유효성을 검증하고(인증), 사용자의 정보가 포함된 JWT를 생성합니다.
         2-1.서버에서 비밀 키 or 개인 키를 사용하여 json 객체를 암호화합니다.
         2-2. 서명된 토큰은 일반적으로 응답 헤더(쿠키) 에 포함되어 사용자에게 응답으로 전달됩니다.
3. 사용자는 받은 토큰을 브라우저 쿠키에 저장합니다.
4. 이후 사용자가 요청할 때 Authorization 헤더에 Bearer 토큰으로 JWT를 포함합니다.
5. 서버는 헤더를 매번 확인하며 인증, 만료 기간 확인 등의 절차를 거칩니다. 토큰이 유효하면 서버는 요청된 리소스에 대한 엑세스를 허용합니다.

사진 출처

JWT의 구조

사진 출처

  1. Header - 토큰에 대한 메타데이터가 포함되어 있으며, 토큰의 타입과 서명 알고리즘이 포함됩니다.
{
    "alg": "HS256",
    "typ": "JWT"
}
// 현재 토큰의 타입이 JWT이고, HS256 서명 알고리즘이 적용됨

  1. Payload - 사용자, 토큰에 대한 설명(Claim)을 key-value의 형태로 저장합니다. (암호화되어 있지 않은 정보이기 때문에 민감한 정보를 담지 않습니다)
    a.  iss : 토큰 발급자
    b.  sub : 토큰 제목 - 토큰에서 사용자에 대한 식별 값이 됨
    c.  aud : 토큰 대상자
    d.  exp : 토큰 만료 시간
    e.  nbf : 토큰 활성 날짜
    f.  iat : 토큰 발급 시간
    g.  jti : JWT 토큰 식별자
{
    "sub": "1234567890",
    "name": "John Doe",
    "admin": true,
    "iat": 1516239022
}

  1. Signature - 토큰의 무결성을 보장하고, 유저의 진위 여부를 확인합니다. 헤더와 페이로드를 인코딩하고, 비밀 키 or 개인 키로 서명하여 생성합니다.
    • 서버가 가지고 있는 개인키를 가지고 Signature를 복호화한 다음 base64UrlEncode(header) 가 JWT 헤더 값과 일치하는지, base64UrlEncode(payload) 와 일치한지 확인한 후 일치한다면 인증을 허용합니다.
HMACSHA256(
    base64UrlEncode(header) + "." + base64UrlEncode(payload),
    secret
)

JWT 사용의 장점

  1. 서버가 세션 데이터를 저장할 필요가 없어 서버 용량에 영향을 끼치거나 받지 않습니다.
  2. 서버가 토큰을 인증하는데 서명 키만 필요하므로 여러 서비스가 상호작용하는 분산 시스템에 적합합니다.
  3. Base64 인코딩 방식을 사용하면 JWT를 HTTP 헤더로 간단하게 전송하므로 휴대성이 뛰어납니다.
  4. JWT에 서명하여 데이터 무결성과 신뢰성을 보장하고, 여러 도메인에서 통신 가능합니다.
  5. 모바일 앱에서 사용하기 적합합니다.
    5-1. 웹 브라우저와 달리 모바일 앱에서는 세션 쿠키를 관리하는 내장된 장치가 없어 개발자가 직접 세션을 관리해야 합니다.
    5-2. 토큰은 iOS에서는 keychain, Android에서는 SharedPreferences와 같은 저장소에 토큰을 저장합니다.

JWT 사용의 문제점

  1. Payload에 너무 많은 정보를 저장하면, 토큰 크기가 커져 네트워크 부하가 심해질 수 있습니다. 또한 Payload에 민감한 정보를 저장해서도 안됩니다.
  2. 토큰은 일반적으로 수명이 짧고, 발급되면 만료 기간 변경이 불가합니다. ⇒ 재로그인 없이 새 JWT를 발급하는 토큰 만료 처리를 따로 구현해야 합니다.
  3. 토큰이 탈취당하면 공격자가 해당 토큰이 만료될 때까지 사용할 수 있습니다. ⇒ exp 즉 만료 시간을 짧게 주어야 합니다. 하지만 만료 시간을 짧게 주면 로그인을 자주 해야하는 사용자의 불편함이 증가합니다.
    3-1. Sliding Session : 사용자가 글쓰기, 결제 등과 같은 특정 행동을 할 때 새롭게 만료 기간을 늘린 JWT를 다시 제공하면서 만료시간을 연장하는 방법입니다. (접속이 단발성으로 일어나면, Sliding Session 으로 연장할 수 없는 상황이 발생할 수 있습니다.)
    3-2. Refresh Token : JWT를 처음 발급할 때 Access Token과 Refresh Token을 함께 발급하여 짧은 만료 시간을 해결하는 방법입니다. Refresh Token을 통해 Access Token이 만료되면 서버에서 새로운 Access Token을 발급하도록 요청합니다.


PASETO

PASETO는 인증 / 인가 에 필요한 유연성을 유지하는 동시에 JWT가 가지고 있는 몇 가지 취약점을 해결해줍니다.

  • 동작 방식은 JWT 토큰의 동작 방식과 거의 유사합니다.
  • JWT는 보안적으로 위험도가 높은 알고리즘도 허용하는 등 보안 알고리즘을 선택함에 있어 개발자의 자유도가 높은 반면, PASETO는 강력하고 현대적인 암호화 알고리즘의 사용을 강제합니다.
    • JWT : HS256 알고리즘을 사용하며 alg header가 설정되어 있으면 HS256 + 서버의 public key 로 인증 요청을 처리하는데 이때 token authentication이 통과하여 해커가 200 OK를 받을 수 있음 ⇒ 서버 측에서 header 의 alg 값이 정의한 것과 다르게 오지는 않았는지 체크가 필요함
    • PASETO는 XCahCah20 - Poly1305 (대칭) / Ed25519 (비대칭)과 같은 보안에 강력한 알고리즘으로 선택을 제한함

PASETO의 구조

local 대칭키

[Version].[Purpose].[Payload].[Footer]
  1. Version : 사용중인 paseto 버전을 말합니다. v1(레거시 코드에 사용) / v2(최신 코드에 사용) 으로 나뉘어집니다.
  2. Purpose : 토큰의 유형을 지정합니다. local (대칭키 알고리즘) / public 으로 나뉘어집니다.
  3. Payload : 토큰 내의 실제 데이터, claim을 포함합니다. secret key로 해독하면 크게 3가지로 나뉘어 집니다.
    3-1. Body : Payload 의 본체, 간단한 메세지와 만료 시간을 포함
    3-2. Nonce : 암호화 및 메시지 인증 프로세스에 사용되는 nonce 값
    3-3. Authentication tag : encrypt 메시지, 관련된 unencripted 데이터를 인증하는 태그
  4. Footer : 공개된 정보들로 base64 인코딩된 채로 제공합니다. (optional)

public 비대칭키

[Version].[Purpose].[Payload]
  1. Version : 사용중인 paseto 버전을 말합니다. v1(레거시 코드에 사용) / v2(최신 코드에 사용) 으로 나뉘어집니다.
  2. Purpose : public으로 지정합니다. 비대칭키 알고리즘을 사용하며 페이로드는 암호화되지 않고 base64 인코딩 됩니다.
  3. Payload : 토큰 내의 실제 데이터, claim을 포함합니다.
    3-1. Body : Payload 의 본체, base64로 인코딩
    3-2. Signature : 개인키로 서명된 토큰의 서명. 공개키를 사용하여 검증

PASETO 사용의 장점

  1. 안전하지 않거나, 더 이상 사용되지 않는 알고리즘을 사용하지 않아 개발자가 위험한 선택을 할 수 없도록 합니다.
  2. 알고리즘을 선택하지 않아 구현이 쉽고, 편리합니다.

PASETO 사용의 문제점

  1. JWT에 비해 커뮤니티 생태계가 부족하고, 이용 가능한 라이브러리도 적습니다.


인증을 위해 고려해볼 사항

  1. HTTPS 사용 : 모든 데이터가 암호화되어 man-in-the-middle attack 으로부터 보호됩니다.
    1-1. 모든 환경(개발, 스테이징, 프로덕션)을 HTTPS로 구성합니다.
    1-2. 모든 HTTP 요청을 HTTPS로 리다이랙션 합니다.
    1-3. 강력한 TLS 버전을 사용합니다.
  2. 강력한 비밀번호 정책 사용 : 취약한 비밀번호는 brute-force attack이나 credential-stuffing attack의 타겟이 되기 쉽습니다.
    2-1. 최소 길이 (12 자 이상)을 요구합니다.
    2-2. 복잡성 규칙(대문자, 소문자, 특수 문자, 숫자)를 포함합니다.
  3. MFA 구현 : 비밀번호 외에 추가적인 확인 방법을 요구하여 보안을 강화합니다.
    3-1. Google Authenticator와 같은 앱을 통한 일회용 비밀번호 도입(TOTP)
  4. 안전한 토큰 관리법 도입
    4-1. exp(기한) 이 짧은 단기 토큰을 사용합니다.
    4-2. 기본 자격 증명을 노출하지 않고 재인증을 위한 refresh token을 도입합니다.
    4-3. JavaScript의 접근을 방지하기 위해 HttpOnly 쿠키를 사용합니다.
    4-4. LocalStorage나 SessionStorage에 토큰을 저장하지 않습니다.
    4-5. CSRF 공격을 완화하기 위해 Samesite 속성을 적용합니다.


+프론트엔드는 access token과 refresh token을 각각 어디에 저장해야 할까요?

1. 로컬 스토리지 / 세션 스토리지

브라우저 자체에 저장하는 경우입니다.

  • 로컬 스토리지는 영구적으로 브라우저에 저장합니다.
  • 세션 스토리지는 브라우저가 켜져있는 동안은 영구적으로 저장되지만, 브라우저가 꺼지면 사라집니다.

access token이나 refresh token을 로컬 스토리지 / 세션 스토리지에 저장하게 되면 XSS 공격에 취약합니다.

⇒ 콘솔창에 JavaScript 코드를 삽입하여 access toekn / refresh token을 탈취하기 쉽습니다.

2. secure httpOnly 쿠키

브라우저에 쿠키로 저장되는데, 클라이언트가 요청을 보낼때마다 자동으로 쿠키가 서버로 전송됩니다.

다만 JavaScript 내에서 접근이 불가하고, https 접속에서만 동작합니다.

  • JavaScript 내에서는 접근이 불가하지만, API 콜을 보내는 방식(httpOnly 쿠키에 담긴 값들도 함께 보내짐)으로 뚫리기 때문에 세션 ID, access token은 저장하면 안됩니다.
  • refresh token은 사용자의 정보를 가지고 있지 않고, access token을 발급해주는 역할만 하기 때문에 쿠키에 보통 저장합니다.

3. js 인메모리(변수)

js 메모리, 즉 변수에 access token이나 세션 ID를 저장합니다.

  • api 요청 시에 header에 직접 accessToken을 넣어줘야 합니다.
  • 새로고침 시에 메모리가 전부 사라지므로 쿠키에 있는 refresh token을 통해 access token을 새로 받아오는 로직이 필요합니다. (구현 난이도 상승)
  • 쿠키에 access token이 들어가지 않고, 직접 넣어주기 때문에 악의적인 사이트에서 api 요청을 해도 자동으로 들어가지 않아 CSRF공격을 막습니다.
    • 쿠키에 refresh 토큰을 넣어주는 이유는, 새로고침 or url로 직접 입력 등으로 브라우저에 새로 진입할 때 로그인 정보가 휘발되는 것을 막아주어야 하기 때문입니다.(UX 관점)
    • refresh token을 탈취하여 access token을 발급받아도, 브라우저에서 access token을 사용하는 것이 아닌 리액트 앱 (axios) 에서 사용하기 때문에 문제가 발생하지 않습니다. (보안 관점)

참고 자료

https://substack.com/home/post/p-152593729

https://brunch.co.kr/@jinyoungchoi95/1

https://btcd.tistory.com/40

https://93960028.tistory.com/66

profile
소통하며 성장하는 프론트엔드 개발자 이승섭입니다! 👋

0개의 댓글