JWT(Json Web Token)에 대해서

마이구미·2025년 9월 17일

CS스터디

목록 보기
1/3
post-thumbnail

1. Jwt란?

정의

JSON 형식의 데이터를 주고 받기 위한 토큰

JWT는 Json Web Token으로 사용자 인증 또는 정보 전달을 위해 사용되는 토큰 형식의 표준 규격입니다.

  • Json은 JavaScript Object Notation으로 "key"와 "value" 형식을 가진 문자 기반 데이터 형식으로 프로그래밍 언어와 상관 없이 사용할 수 있습니다.
// JSON 데이터 예시

{
	"name": "홍길동",
    "age": 33
}
  • JWT에서 Token은 인증 상태나 권한 정보를 담아 디지털 서명이 된 데이터 조각입니다.

구조

Header.Payload.Signature

JWT는 'Header', 'Payload', 'Signature' 3개의 부분으로 나누어져 있고 .으로 구분 되어집니다.

Header(헤더)

토큰의 서명되어 있는 방식을 나타내며 "alg", "type" 등의 정보가 들어갑니다.

"alg": 사용할 서명 알고리즘 (ex. HS256)
"typ": 토큰의 type (ex. JWT)
"kid": 서명시 사용하는 키 (ex. key1, 키가 여러개일 경우 사용 - 필수는 아님)

Payload (페이로드)

전달하고자 하는 실제 정보가 들어가고 이 정보를 Claim(클레임)이라고 부릅니다.

Claim은 저장되는 정보에 따라 "Registered", "Public", "Private"으로 나누어집니다.

Registered

JWT 표준에서 정의한 예약어입니다. 아래는 몇 개의 예시입니다.

  • iss: 발급자 (ex. http://...)
  • sub: 주체 (ex. user_ex92...)
  • aud: 대상자 (ex. api://...)
  • exp: 만료시간 (ex. 169521000)
  • nbf: 이전 시간에 사용을 금지 (ex.169521000)
  • iat: 발급 시간 (ex. 169521000)
  • jti: JWT ID (ex. 7b.....)

Public

개발자가 자유롭게 사용 가능한 클레임입니다. 사용자 요금제, 조직 정보 등에 사용합니다.

Private

서비스 내부에서만 사용하는 커스텀 데이터입니다. 아래는 몇 개의 예시입니다.

  • role: 역할 (ex. admin)
  • permission: 권한 (ex. read)
  • amr: 인증 수단 (ex. pwd)
  • ip: 발급 시점 ip (ex. 198.2....)

Signature (서명)

Header와 Payload를 base64 인코딩 후 비밀키와 함께 서명 알고리즘을 적용하여 생성합니다. 이를 통해 토큰의 위조 여부를 확인 할 수 있습니다.

최종적으로는 base64 형식의 header.payload.secret이 됩니다.

동작 방식

  1. 로그인 요청
  2. 서버가 사용자 인증 → JWT 발급
  3. 클라이언트가 JWT를 저장
  4. 이후 발급한 JWT를 포함하여 서버에 전송
  5. 서버는 JWT의 서명을 검증하여 사용자 인증 처리
  6. 서버에서 응답 반환
  7. 만약 Access와 Refresh 토큰을 나누었다면 Refresh 토큰을 통해 Access Token 재발급 요청
  8. Access Token 재발급

특징

장점

  1. Stateless(무상태)
    토큰 자체에 사용자 정보와 만료 시간이 포함되어 있어 서버가 세션 정보를 저장하지 않아도 됩니다. 따라서 서버 확장에 유리합니다.

  2. 빠른 인증 처리
    DB 조회 없이 서명 검증만으로 토큰 유효성을 판단하기 때문에 속도가 빠릅니다.

  3. 범용성
    Json 기반이라 언어와 플랫폼에 상관없이 사용이 가능하며, 웹 모바일 등의 다양한 환경에서 사용할 수 있습니다.

단점

  1. 민감 정보 노출 위험
    Payload는 Base64 형태의 인코딩이기에 누구나 디코딩을 하여 정보를 확인할 수 있습니다. 따라서 민감한 정보를 Payload에 저장하면 위험할 수 있습니다.

  2. 토큰 탈취의 위험성
    탈취된 토큰은 만료전까지 누구나 사용할 수 있기 때문에 토큰을 안전하게 보관할 필요가 있습니다. 특히 Http 환경에서는 토큰을 평문으로 전송하기 때문에 공격자가 네트워크를 도청하면 "Authorization" 헤더에 있는 토큰을 그대로 탈취하여 사용할 수 있습니다. 따라서 클라이언트와 서버의 통신을 TLS로 암호화하는 Https를 사용하여 네트워크 경로에서의 토큰 유출을 방지할 필요가 있습니다.

  3. 토큰 크기
    Header.payload.Signature 구조로 세션 ID보다 길이가 길어 네트워크 트래픽 증가가 가능하다.

  4. 회수와 로그아웃의 어려움
    이미 발급된 토큰은 만료전까지 유효하다. 따라서 토큰을 무효화하기 위해서는 jti를 이용한 블랙리스트 관리가 필요합니다. 또한 Refresh 토큰을 이용하는 전략도 있습니다.

2. 쿠키, 세션 인증 방식과 JWT와의 비교

쿠키(Cookie)

정의

쿠키는 인증 방식이 아닌 Key와 Value를 가지는 데이터 조각입니다. 클라이언트가 웹 사이트를 방문할 때 사이트 자체의 서버를 통해 브라우저에 설치되는 정보 기록 파일입니다.

쿠키는 미리 정해진 기간 동안 사용자의 정보를 저장합니다. 서버가 응답 헤더에 set-Cookie를 내려주면 브라우저가 쿠키를 저장하여 이후 클라이언트가 요청을 할 때 자동으로 헤더에 쿠키를 꺼내서 넣습니다.

흐름

  1. 클라이언트가 서버에 로그인을 요청합니다.

  2. 서버가 로그인 성공 후 자신을 식별할 수 있는 쿠키를 발급해 응답 헤더에 Set-Cookie의 지시를 내립니다. (명령어로 인해 브라우저가 쿠키를 저장합니다.)

  3. 이후 요청에 자동으로 쿠키가 전송되어집니다.

  4. 서버가 쿠키를 읽어 요청을 처리하고 응답을 반환합니다.

특징

장점

  1. 자동 전송
    브라우저가 조건에 맞으면 요청을 보낼 때 Cookie 헤더에 넣어 자동으로 서버로 전송되어지기에 개발자가 매번 토큰을 헤더에 넣을 필요가 없어 편리합니다.

  2. 브라우저 기본 지원
    별도의 구현 없이 브라우저에서 기본으로 지원해줍니다.

  3. 지속성
    "Expires"나 "Max-Age"를 설정하면 브라우저를 종료했다가 다시 켰을 때도 유지가 가능합니다. (따라서 로그인이나 사용자 환경설저에 유리합니다.)

단점

  1. 보안에 취약함
    기본적으로 브라우저에 평문으로 저장되어 지고 요청 시 쿠키의 값을 그대로 보낼 수도 있기 때문에 유출 및 조작 당할 위험이 있습니다.

  2. XSS에 취약
    "HttpOnly" 속성을 사용하지 않으면 악성 스크립트를 이용한 공격에서 "document.cookie"로 쿠키를 탈취 당할 수 있습니다.

  3. CSRF 위험
    사용자가 의도하지 않은 요청을 자동으로 서버에 보내 쿠키를 자동으로 붙이도록 하는 CSRF 공격에 취약합니다. ("SameSite 옵션 등의 방어책이 필요합니다.)

  4. 외부 API 및 모바일 환경에서는 부적합
    모바일이나 외부 API에서는 Authorization 헤더 + 토큰 방식을 선호하기에 적합하지 않습니다.

  5. 용량 및 개수 제한
    쿠키는 1개당 4KB, 도메인당 수십~수백 개 제한이 많은 데이터 저장에는 부적합합니다.

  6. 네트워크 트래픽 증가
    요청마다 자동으로 전송되어 지기 때문에 불필요하게 트래픽이 낭비될 수 있습니다.

한계

  1. 보안 문제
    쿠키 값은 사용자가 마음대로 바꿀 수 있어 신뢰하기가 어렵습니다.

  2. 민감정보 노출
    비밀번호와 같은 민감한 정보를 쿠키에 담으면 요청마다 노출되므로 탈취될 위험이 큽니다.

  3. 서버 쪽 검증 부제
    쿠키 자체에 대한 검증 로직이 존재하지 않아 이것이 유효한 쿠키인지 확인할 방법이 없습니다.

이러한 한계점을 보완하기 사용하는 것이 세션입니다.

세션 (Session)

정의

클라이언트와 브라우저 사이의 상태를 유지하기 위한 메커니즘입니다. 로그인 상태를 서버가 직접 저장하고 클라이언트가 매핑할 수 있는 세션 ID를 부여하는 방식입니다.

흐름

  1. 서버에서 로그인을 하면 세션 저장소에 정보를 저장하고 해당 사용자와 연결된 세션ID를 생성합니다.

  2. 서버 응답시 Set-Cookie로 세션 ID를 브라우저에 전달합니다.

  3. 브라우저는 요청마다 쿠키에 session_id를 포함하여 보냅니다.

  4. 서버는 세션 ID를 바탕으로 사용자 정보를 조회하여 로그인 상태를 확인합니다.

특징

장점

  1. 로그아웃과 회수가 쉬움
    서버에서 세션을 삭제하면 즉시 무효화가 되기 때문에 탈취 피해를 빠르게 막을 수 있습니다.

  2. 안정성
    클라이언트에 민감 정보를 직접 저장하지 않고 서버에서 관리하기 때문에 더 안전합니다.

단점

  1. 확장성 문제
    서버를 여러 대 운영할 경우 세션을 공유해야 합니다.

  2. 서버 부하
    모든 요청마다 세션 저장소를 조회하기 때문에 대규모 트래픽에서 병목 현상이 일어날 가능성이 높습니다.

  3. 모바일 및 외부 API에는 비적합
    쿠키를 이용하기 때문에 외부 API나 모바일에서는 맞지 않습니다.

JWT 인증과의 차이점

  1. 세션 인증에서는 서버가 세션 저장소에 상태를 저장하고 JWT는 토큰에 토큰에 정보가 포함되어 있는 무상태 방식입니다.

  2. 세션 인증은 세션 ID를 쿠키에 저장하고 JWT는 스스로를 쿠키나 스토리지에 저장할 수 있습니다.

  3. 세션 인증은 서버 확장 시 세션 공유가 필요하지만 JWT 인증은 서버간 동기화가 필요하지 않아 확장성이 높습니다.

  4. 세션 인증의 세션 ID는 JWT보다 크기가 작습니다.

3. Jwt를 어디에 저장하는게 좋을까?

저장할 토큰의 종류

Access Token

Access Token은 탈취 시 피해를 최소화하기 위해 15분~1시간 정도의 짧은 시간을 가지는 토큰으로 보통 쿠기에 저장합니다.

Reflesh Token

Reflash Token은 Access Token의 재발급 용도로 사용되어 지며 수명이 깁니다. 따라서 이 토큰은 서버가 반드시 저장하고 관리해야 합니다.

저장할 위치

LocalStorage

특징

영구적으로 저장이 가능하고 용량이 크며 JavaScript로 접근이 가능합니다.

장점

  1. 브라우저나 탭을 닫아도 유지됩니다.

  2. 구현이 간단합니다.

단점

  1. 쿠키와 달리 HttpOnly 옵션이 없어 XSS 공격에 취약합니다.

결론

토큰 저장 용도로는 보안 이슈가 있어 사용을 권장하지 않습니다.

sessionStorage

특징

탭 단위의 저장이며, 브라우저나 탭을 닫으면 정보가 사라집니다.

장점

  1. 자동으로 만료되어 민감한 서비스에 유리합니다.

  2. localStorage에 비교해서 로그인 유지에 대한 부담이 적습니다.

단점

  1. JS를 통한 XSS 공격에 취약합니다.

결론

토큰 저장 용도로는 보안 이슈가 있어 사용을 권장하지 않습니다.

HttpOnly 옵션 + 쿠키

특징

서버가 Set-Cookie로 내려주고 자동으로 관리해줍니다.

장점

  1. HttpOnly 옵션을 이용하여 JS를 이용한 접근을 차단하여 XSS를 이용한 탈취를 막을 수 있습니다.

  2. Secure를 이용하여 토큰이 Https 환경에서만 전송이 되도록 설정할 수 있습니다.

  3. SameSite를 이용하여 CSRF로 인한 탈취를 완화 할 수 있습니다.

  4. 요청마다 자동으로 전송이 되기 때문에 개발 편의성이 증가합니다.

단점

  1. CSRF 공격이 완화되기는 하지만 여전히 위험이 있습니다.
    (기본 모드인 Lax에서는 외부 링크 클릭의 GET 요청과 같은 경우에는 여전히 쿠키가 전송되어 집니다. 완벽하게 막을 수 있는 Strict 모드가 있으나 외부 링크를 클릭하면 로그인이 풀리는 단점이 있어 실무에서는 잘 사용하지 않는 것 같습니다.)

결론

보안과 실무적 안정성 측면에서는 적합하다고 생각하기에 AccessToken을 저장하는 용도로 사용하면 좋을 것 같습니다.

Redis (In-memory Cache)

정의

모든 데이터를 RAM에 올려두고 빠르게 읽고 쓰는 key-Value DB입니다. 필요하면 디스크에 저장도 가능합니다.

TTL 기능을 이용하여 데이터가 자동 만료되도록 설정할 수 있습니다. 기본적으로는 메모리에 있는 동안 저장이 되어 지며, 특별한 조치를 하지 않으면 서버 재부팅 시 데이터가 날아갈 수 있습니다.

영구 저장 및 재부팅 시 초기화 방지

  1. RDB SnapShot
    일정 주기마다 메모리 상태를 스냅샷으로 찍어서 디스크에 저장합니다. 해당 방식을 사용하며 디스크 부하는 적지만 스냅샷 이후 변경된 데이터는 반영되지 않을 수 있습니다.

  2. AOF (Append Only File)
    모든 쓰기 명령을 로그 파일에 순차적으로 기록합니다. 서버 재시작시 AOF 로그를 재 실행하여 메모리 상태를 복구합니다. 해당 방법은 손실을 최소화할 수 있지만 파일 크기가 계속해서 커지기에 주기적으로 재압축이 필요합니다.

  3. Hybrid
    스냅샷으로 빠른 복구 후에 AOF로 데이터 유실 최소화를 진행합니다.

장점

  1. 빠른 속도
    메모리 기반으로 조회와 삭제가 빨라 인증 서버에 적합합니다.

  2. 만료 관리 자동화
    TTL 기능을 통해 Redis 자체적으로 만료 시간을 설정할 수 있습니다.

  3. 블랙리스트 관리 편리
    탈취된 토큰을 빠르게 차단할 수 있습니다.

  4. 분산 환경에 유리
    여러 환경에서 공통 Redis를 사용하면 세션이나 토큰의 공유에 유리해집니다.

단점

  1. 메모리 기반이라 대량의 정보를 보관할수록 비용이 커집니다.

  2. 서버 장애시 데이터 손실이 발생할 수 있습니다.

결론

일반적인 경우에는 Redis가 유리할 것 같습니다. 속도가 RDB보다 더 빠르면서 TTL을 통해 자동으로 만료시간을 관리해주기 때문에 사이트에 로그인을 하였을 때 자동으로 로그아웃을 통해 정보를 보호할 수 있기 때문입니다.

RDB (Relational DataBase)

특징

테이블, 행, 열 구조이며 SQL로 조회합니다. 디스크에 데이터를 저장합니다.

장점

  1. 장애나 재 부팅 시에도 데이터가 보존됩니다.

  2. 누가 언제 어떤 Refresh Token을 발급 받았는 지에 대한 이력을 관리할 수 있습니다.

  3. 사용자 테이블에 연관 관계를 설정하여 편리하게 관리할 수 있습니다.

단점

  1. 조회, 삭제 속도가 Redis 보다 느립니다.

  2. 만료 처리를 직접 구현해야 합니다.

결론

누가 발급했는 지에 대한 추적이 필요한 경우와 장기간 데이터를 보관하는 경우네는 RDB가 유리할 것 같습니다.

Redis, RDB 관련 보안 이슈

  1. 토큰 탈취 위험
    공격자가 Db나 Redis에 접근하여 Refresh Token을 통째로 빼갈 수 있기 때문에 토큰을 암호화나 해싱을 통해 저장하는 방법이 있습니다. 이는 다시 쿠키 세션에 token의 원문이 노출되어 위험해 보이지만 토큰이 탈취되었을 때 db에 있는 정보를 삭제하는 것으로 추가적인 access_token의 발급을 막아 빠르게 피해를 줄일 수 있습니다.

  2. 만료 기간 미설정으로 위한 위험
    Redis에서 TTL을 통한 자동 만료나 RDB에서 expires_at을 통한 만료 검사를 통해 검증하는 것이 필요합니다.

  3. Redis의 기본 암호화 부재
    Redis는 기본 암호화가 부재하여 내부망이 뚫리면 데이터가 그대로 탈취당할 위험이 있으며, 인증도 걸지 않으면 누구나 접근할 수 있습니다. Redis도 평문 통신이라 같은 네트워크에서 패킷을 가로채면 데이터가 노출 될 수 있기 때문에 TLS를 활성화하여 Redis와 서버 간 연결도 암호화 할 수 있습니다. Redis에서 ACL을 통해 접근 권한을 설정할 수 있어 사용자 계정과 권한을 두고 최소 권한 원칙을 적용하는 방법이 있습니다. VPC 내부망에서만 접근을 활성화하여 외부에서 바로 접근하는 것을 막을 수도 있습니다.

  4. DOS
    RDB를 사용할 때 가짜 refresh token을 통해 수십만개의 요청을 보내 서비스 처리에 과부하가 걸려 정상 처리도 못하는 경우입니다. Redis 캐시를 이용하여 빠르게 검증하거나 사용자나 아이피별 요청 횟수를 제한하는 방법이 있습니다. 또한 해시 알고리즘을 튜닝하여 보안성과 성능의 균형을 맞추는 것도 하나의 방법입니다. 또한 서면 검증과 redis를 이용한 이중 보안도 하나의 방법이 될 수 있습니다.

  5. SQL Injection
    sql injection을 통해 탈취를 시도할 수 있습니다. 이 때는 Prepared statement나 최소 권한 원칙 적용을 합니다

결론
보완과 성능의 균형을 맞추는게 필요합니다. Refresh Token을 해시화해서 저장하는방법과 Redis의 TTL, Persistance, TLS와 ACL를 적절히 이용하는 것도 필요해 보입니다. 또한 RDB의 sql injection을 방지하는 것도 필요합니다. 또한 속도의 향상을 위해 Redis를 이용하여 속도를 챙기고 RDB를 이용하여 정보를 추적하는 것도 좋다고 생각합니다.

profile
개발 입문한 초보입니다

0개의 댓글