JWT 개념 정리하기

JWT 개념 정리하기

2025-04-21
0
views

더 유연하고 확장성이 좋다는 이유로 JWT를 사용한 토큰 기반 인증 방식을 많이 사용하고 있습니다. 하지만 JWT에 대한 이해가 부족한 상태에서 그냥 사용하다 보니, 관련된 문제 상황이나 질문을 마주했을 때 어려움을 겪게 되더군요.

그래서 이번 기회에 JWT의 기본 개념부터 하나하나 정리해보려 합니다. 저처럼 JWT에 대한 이해가 부족해 답답함을 느꼈던 분들에게 이 글이 작은 도움이 되었으면 좋겠습니다.

JWT 란?

JWT는 JSON Web Token의 약자로, JSON 객체를 사용하여 정보를 안전하게 전송하기 위한 개방형 표준입니다.

주로 토큰 기반 인증 및 권한 부여에 사용되며, 클라이언트와 서버 간의 정보를 안전하게 교환하는 데 사용됩니다.

세션 기반 인증과 달리 JWT는 서버에 상태를 저장하지 않기 때문에 무상태(stateless) 인증 방식으로 분류됩니다.

JWT는 다음과 같은 장점이 있습니다:

  1. 확장성: 서버에 상태를 저장하지 않기 때문에 수평적 확장이 용이합니다.
  2. 보안성: 서명된 토큰을 사용하여 데이터의 무결성을 보장합니다.
  3. 유연성: 다양한 클레임을 포함할 수 있어 다양한 사용 사례에 적합합니다.

JWT 구성요소

JWT는 위의 세 가지 부분을 점(.)으로 구분하여 연결한 문자열로 구성됩니다.

debugger-screenshot jwt.io의 스크린샷

1. Header (헤더)

헤더는 JWT의 타입서명 알고리즘을 정의하는 부분입니다. 헤더는 JSON 형식으로 작성된 뒤 Base64Url로 인코딩되며, JWT의 첫 번째 부분에 포함됩니다.

예를 들어 아래와 같이 alg로 사용된 서명 알고리즘은 HMAC SHA256임을 정의하고, typ으로 JWT 타입을 정의할 수 있습니다.

JWT Header example
{
  "alg": "HS256",
  "typ": "JWT"
}

2. Payload (페이로드)

페이로드는 JWT에 담을 **클레임(Claims)**을 포함하는 부분입니다. 클레임이란 사용자의 정보, 권한 등과 같은 데이터를 의미하며, 인증 및 권한 부여에 활용됩니다. 페이로드는 JSON 형식으로 작성된 뒤 Base64Url로 인코딩되며, JWT의 두 번째 부분에 포함됩니다.

JWT Payload example
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

이때, 페이로드는 암호화되지 않기 때문에 토큰만 있으면 누구나 내용을 확인할 수 있습니다. 따라서 비밀번호나 개인정보처럼 민감한 정보는 포함하지 않아야 합니다.

필요한 경우 페이로드를 암호화하여 보안을 강화한 기술인 **JWE(JSON Web Encryption)**를 사용할 수 있습니다.


3. Signature (서명)

서명JWT가 위변조되지 않았는지 확인하는 데 사용되는 중요한 부분입니다. 헤더와 페이로드를 합친 문자열을 비밀 키로 서명하여 생성되며, 서버는 이를 통해 토큰의 무결성을 검증할 수 있습니다.

JWT Signature generation
HMACSHA256(base64UrlEncode(header) + '.' + base64UrlEncode(payload), secret);

사용되는 서명 알고리즘은 HS256(HMAC), RS256(RSA) 등 다양하며, 이 서명 역시 JWT의 세 번째 부분에 포함됩니다.


JWT 클레임의 종류

JWT 클레임은 JSON 형식을 따르기 때문에 자유롭게 정의할 수 있습니다. 하지만 JWT의 표준에 따라 미리 정의된 클레임이 있습니다. 이 클레임들은 JWT의 구조와 의미를 명확히 하기 위해 사용됩니다.

1. Registered Claims (등록된 클레임)

등록된 클레임은 JWT 명세(RFC 7519 Section 4.1)에 따라 표준으로 정의된 클레임입니다. JWT의 의미와 사용 목적을 더 명확히 하기 위해 존재하며, 다음과 같은 필드를 포함합니다:

  • iss (issuer): 토큰을 발급한 주체
  • sub (subject): 토큰의 주제 또는 사용자 ID
  • aud (audience): 토큰 대상
  • exp (expiration): 만료 시간

2. Public Claims (공개 클레임)

공개 클레임외부 시스템과 데이터를 주고받을 때 사용되는 클레임입니다. 다른 시스템과의 충돌을 방지하기 위해 다음 중 하나의 방식을 따라야 합니다:

  1. IANA/JWT Claims에 등록된 이름 사용

    • RFC 7519 Section 10.1에 따라 공개 클레임의 표준을 IANA에서 관리합니다.
    • 예: email, name, roles
  2. 충돌 방지 이름 사용

    • IANA에 등록되지 않은 이름을 사용할 경우, https://example.com/custom_claim처럼 고유 URI를 포함해 네임스페이스를 지정합니다.

3. Private Claims (비공개 클레임)

비공개 클레임특정 애플리케이션 내부에서만 사용되는 클레임입니다. 서로 협의된 시스템 간에만 의미가 있으므로, 충돌 방지 이름을 사용할 의무는 없습니다.


JWT 활용 방식

JWT는 다양한 방식으로 인증에 활용될 수 있습니다. 그 중 몇 가지를 소개합니다:

1. AccessToken 단일 방식

auth-flow.png

가장 기본적인 JWT 인증 방식으로, 서버가 사용자 인증에 성공하면 AccessToken만 생성하여 클라이언트에 전달합니다. 이 토큰은 클라이언트에서 로컬 스토리지나 쿠키에 저장되고, 이후 요청마다 포함되어 서버로 전달됩니다.

인증 과정

  1. 클라이언트가 사용자 인증 정보(id, password 등)를 서버에 전송합니다.
  2. 서버는 인증 정보를 확인한 후 AccessToken을 생성하여 클라이언트에 반환합니다.
  3. 클라이언트는 AccessToken을 저장하고, 이후 요청 시 AccessToken을 포함하여 서버에 전송합니다.
  4. 서버는 AccessToken을 검증하여 요청을 처리합니다.

장점

  • 구조가 단순하여 구현이 쉬움
  • 서버에서 아무런 정보도 저장하지 않기 때문에 수평적 확장이 용이함

단점

  • AccessToken이 만료되면 재인증이 필요함
    • 만료 시간을 짧게 설정하면 사용자 경험이 나빠짐
    • 만료 시간을 길게 설정하면 보안성이 낮아질 수 있음
  • 토큰 탈취에 대응할 수 있는 방법이 없음

2. AccessToken + RefreshToken 방식

1번 방식의 단점을 보완하기 위해 RefreshToken을 추가하여 사용하는 방식입니다. 이 방식은 AccessToken이 만료된 경우, RefreshToken을 사용하여 새로운 AccessToken을 발급받는 과정을 포함합니다.

인증 과정

  1. 클라이언트가 사용자 인증 정보(id, password 등)를 서버에 전송합니다.
  2. 서버는 인증 정보를 확인한 후 AccessToken과 RefreshToken을 생성하여 클라이언트에 반환합니다.
  3. 클라이언트는 AccessToken과 RefreshToken을 저장하고 이후 요청 시 AccessToken을 포함하여 서버에 전송합니다.
  4. AccessToken이 만료되면 클라이언트는 RefreshToken을 사용하여 새로운 AccessToken을 요청합니다.
  5. 서버는 RefreshToken을 검증한 후 새로운 AccessToken을 발급합니다.
  6. 다시 3번으로 돌아가서 AccessToken을 사용하여 요청을 처리합니다.

장점

  • 만료 시간을 짧게 설정한 AccessToken으로 인한 보안 강화를 하면서도 UX를 해치지 않음
  • RefreshToken을 HttpOnly 쿠키에 저장하면 JS로 접근 불가능하므로 탈취 방지에 유리함
  • 서버가 RefreshToken을 저장하면 탈취 시 무효화도 가능함
    • whitelist 방식 : 발급한 토큰을 서버에서 저장하고 저장되지 않은 토큰은 무효화하는 방식
    • blacklist 방식 : 사용자 로그아웃 등 차단할 때, 서버에서 해당 토큰을 블랙리스트에 추가하여 무효화하는 방식

단점

  • 구조가 복잡해짐
  • 서버에 RefreshToken을 저장해야 하므로 상태를 저장하지 않는 JWT의 장점이 사라짐
  • RefreshToken이 탈취되면 결국 보안에 취약해짐
    • 이 경우, RefreshToken을 안전하게 저장하는 방법이 필요함
    • 예를 들어, HttpOnly 쿠키에 저장하여 JS에서 접근할 수 없도록 하는 방법이 있음

3. RefreshToken Rotation 방식

RefreshToken Rotation은 RefreshToken 탈취에 대비하기 위한 고급 보안 전략입니다. RefreshToken을 사용할 때마다 새로운 RefreshToken을 서버에서 발급하고, 기존 토큰은 즉시 폐기합니다. 이 방식은 탈취된 RefreshToken이 재사용되는 것을 탐지하고 차단할 수 있습니다.

인증 과정

  1. 클라이언트가 사용자 인증 정보(id, password 등)를 서버에 전송합니다.
  2. 서버는 인증 정보를 확인한 후 AccessToken과 RefreshToken을 생성하여 클라이언트에 반환합니다.
  3. 클라이언트는 AccessToken과 RefreshToken을 저장하고 이후 요청 시 AccessToken을 포함하여 서버에 전송합니다.
  4. AccessToken이 만료되면 클라이언트는 RefreshToken을 사용하여 새로운 AccessToken을 요청합니다.
  5. 서버는 RefreshToken을 검증한 후 새로운 AccessToken과 RefreshToken을 발급하고 기존 RefreshToken은 폐기합니다.
  6. 다시 3번으로 돌아가서 AccessToken을 사용하여 요청을 처리합니다.

장점

  • 탈취된 RefreshToken이 재사용되는 것을 탐지할 수 있음
  • RefreshToken이 탈취되더라도, 새로운 RefreshToken을 발급받지 못하므로 보안성이 높아짐
  • 가장 강력한 보안 전략으로, RefreshToken을 안전하게 관리할 수 있음

단점

  • 구현 복잡도가 매우 높아짐
  • 서버에 RefreshToken을 저장해야 하므로 상태를 저장하지 않는 JWT의 장점이 사라짐
  • 네트워크 불안정 등으로 인해 RefreshToken이 재발급되지 못할 경우, 사용자가 로그아웃될 수 있음

4. 그 외의 JWT 보안 강화 전략

4-1. IP 주소 및 User-Agent 검증

  • 방식: RefreshToken 사용 시, 클라이언트의 IP / User-Agent를 기록해두고 비교
  • 장점: 탈취된 토큰이 다른 환경에서 쓰일 경우 차단 가능
  • 단점: VPN, 모바일 IP 변화로 인해 정상 사용자도 차단될 수 있음

4-2. 2FA(2-Factor Authentication)

  • 방식: RefreshToken 사용 시 SMS, 이메일, OTP 등으로 추가 인증 요구
  • 장점: 탈취해도 추가 인증 없이는 무용지물
  • 단점: UX 부담, 사용자 이탈 가능성
  • Tip: 민감 작업(비밀번호 변경, 자산 조회 등)에만 조건부 2FA 적용도 고려 가능

4-3. Sliding Expiration

  • 방식: 사용 시마다 만료 시간 연장 (서버 기준 시간 갱신)
  • 장점: 자주 사용하는 사용자는 로그인 상태 유지 가능
  • 단점: 탈취되면 토큰 생명주기 연장으로 이어져 위험
  • Tip: IP나 디바이스 체크와 함께 사용 시 리스크 낮춤

4-4. Split Token 전략

  • 방식: JWT를 header.payload (클라이언트 저장) + signature (HttpOnly 쿠키)로 분리
  • 장점: 탈취된 payload만으론 사용 불가능, XSS 대비 강함
  • 단점: 클라이언트/서버 간 구현 복잡성 높음
  • Tip: 고보안 환경(은행, 사내 시스템)에서 고려

마치며

JWT는 강력하고 유연한 인증 방식이지만, 각 방식의 장단점을 이해하고 적절히 활용하는 것이 중요합니다. 이 글이 JWT에 대한 이해를 높이고, 보안 강화를 위한 전략을 고민하는 데 도움이 되었기를 바랍니다.