JWT (Json Web Token) study

hong·2024년 12월 5일

조사

목록 보기
13/15

JWT란?

유저를 인증하고 식별하기 위한 토큰(Token) 기반 인증

토큰이란?

인증과 정보 전달을 위해 사용하는 데이터 묶음. 사용자의 권한 정보나 서비스를 사용하기 위한 정보가 포함된다

구조

header, payload, signature로 구성된다.

1) header

키(kid), 타입(typ), 서명 암호화 알고리즘(alg)의 정보가 담겨있다.

예시 :

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "12345"
}

alg : 토큰을 서명할 때 사용된 알고리즘. 여기서는 RS256이 사용됐다.
typ : 토큰의 타입. JWT는 항상 JWT로 설정된다.
kid : key ID.
여기서 키(kid)는 선택적으로 사용할 수 있다.

서명이란?

JWT가 위조되지 않았음을 보증하기 위해, JWT에 고유한 "도장"을 찍는 과정

과정

  1. Header와 Payload를 하나로 합침:
encodedHeader + "." + encodedPayload

//예시
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NTYifQ
  1. 이 데이터를 특정 키로 암호화
    서버는 비밀키를 이용하여 암호화하거나 해시값을 생성하는데, 이 때 생성된 암호화값이나 해시값이 signature(서명)이다.

  2. JWT에 서명을 붙임

encodedHeader + "." + encodedPayload + "." + signature

//예시 
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NTYifQ.abcdef123456
//최종
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NTYifQ.abcdef123456

서명의 목적

  1. 데이터 무결성 검증
  2. 신뢰성 보증

➡️ 서명은 비공개키로 생성되며, 공개키로 검증된다. 클라이언트가 서명을 확인하면, JWT가 변조되지 않았고 신뢰할 수 있다는 걸 알 수 있다.

kid는 왜 선택적인가?

JWT는 비대칭암호화 방식을 사용한다.
비대칭 암호화 : 공개키(누구나 볼 수 있는 키)와 비공개키(소유자만 아는 비밀키)를 사용하며, 비공개키로 암호화하여 공개키로 복호화하거나, 공개키로 암호화하여 비공개키로 복호화하는 방식이다.

JWT에서의 사용
JWT 생성 : 서버는 비공개키로 JWT를 서명한다.
JWT 검증 : 클라이언트나 다른 서버는 공개키를 사용해 JWT 서명을 검증한다. 이 때, 공개키만으로는 서명을 위조하거나 비공개키를 알아낼 수 없다.

이 때, 사용하는 키가 하나뿐이라면 kid는 굳이 쓸 필요 없다.

kid는 언제 사용하는가?

서버에 키가 여러개 있는 경우.

  1. 키 롤링(Key Rotation)을 하는 경우
    보안을 강화하기 위해 오래된 키를 폐기하고 새로운 키를 사용하는 경우.
    6개월마다 새로운 키를 생성한다면, 새 JWT는 새로운 키로 서명되고, 기존 JWT는 이전 키로 검증해야 하는데, 이 경우 kid가 있어야 어떤 키로 서명했는 지 알 수 있다.

  2. 다른 클라이언트별 키 관리
    만약 여러 애플리케이션이나 클라이언트(예: 모바일 앱, 웹 앱)가 서버와 통신한다면,
    각 클라이언트마다 고유한 키를 부여해서 관리할 수 있어 JWT의 kid를 통해 어떤 클라이언트가 보낸 JWT인지 식별 가능하다.

  3. JWT를 발급하는 서버가 여러 개일 때
    부하 분산이나 마이크로서비스 아키텍처에서 JWT 발급 서버가 여러 개라면, 각 서버마다 고유한 키를 사용할 수 있다. 검증할 때는 kid를 통해 어떤 서버의 키를 사용해야 하는지 알 수 있다.
    부하분산: 트래픽을 여러 서버에 고르게 분배해 시스템이 과부하 없이 안정적으로 작동하도록 하는 기술.
    마이크로서비스 아키텍처: 하나의 큰 애플리케이션을 여러 개의 독립적인 작은 서비스로 나눠 각각 관리하고 개발하는 방식. (유지보수와 확장성에 유리함)

  4. 서버가 공개키를 여러개 제공하는 경우
    오래된 공개키와 새로운 공개키가 모두 JWKS(JSON Web Key Set)에 등록돼 있을 때,
    kid를 통해 어떤 공개키를 사용해야 할지 선택하게 된다.
    JWKS (JSON Web Key Set): JSON 형식으로 공개 키(public key) 정보를 담은 문서.
    주로 JWT의 서명을 검증할 때 사용되며, 인증 서버에서 제공하는 URL로 가져올 수 있음.

  5. Access TokenRefresh Token으로 나누어 사용하는 경우
    Access Token: API 요청에 바로 사용되는 단기 토큰.
    Refresh Token: Access Token을 갱신하는 장기 토큰.

왜 Access Token과 Refresh Token으로 나누어 사용하는가?

Access Token이란?

역할:
API 요청 시 인증을 증명하는 데 사용됨.
서버는 Access Token을 보고 사용자가 요청할 권한이 있는지 확인.

특징:
주로 짧은 만료시간(예: 5분~1시간)을 가짐.
유출되면 즉시 API를 호출할 수 있으므로 짧게 유지하는 게 중요.
클라이언트가 보호된 리소스(API)에 접근할 때 함께 전송

Refresh Token이란?

역할:
Access Token이 만료되었을 때, 새로운 Access Token을 발급받는 데 사용됨.

특징:
더 긴 만료시간(예: 7일~30일)을 가짐.
서버에서만 검증하며, API 요청에 직접 사용되지 않음.
유출 시 피해가 클 수 있으므로 반드시 안전한 저장소(예: HttpOnly 쿠키)에 저장.

간단 flow

  • 사용자가 로그인하면 Access Token과 Refresh Token이 발급됨.
  • 클라이언트는 Access Token을 사용해 API 요청.
  • Access Token이 만료되면 Refresh Token으로 새 Access Token 발급.
  • Refresh Token도 만료되거나 유효하지 않으면, 사용자는 다시 로그인해야 함.

그래서 나누는 이유는?

보안 강화:
Access Token이 유출돼도, 만료되면 더 이상 사용할 수 없음.
Refresh Token은 서버에서만 검증되므로, 노출 위험을 줄임.

유연성:
Access Token의 만료시간을 짧게 설정해서 보안을 강화.
Refresh Token으로 사용자가 번거롭게 재로그인하지 않아도 되게 함.

API 요청 효율화:
Refresh Token은 오직 Access Token 갱신에만 사용되므로, API 요청에는 전혀 영향을 주지 않음.

2) payload

토큰에서 사용할 정보의 조각들인 클레임(claim)이 담겨있다.
key:value 형태를 가진다.
저장되는 정보에 따라 등록된 클레임(Reserved Claims), 공개 클레임(Public Claims), 비공개 클레임(Private Claims)로 구분된다.

예시

{
  "iss": "myapp.com",
  "sub": "1234567890",
  "aud": "myapi",
  "exp": 1712345678,
  "iat": 1712345670,
  "role": "admin",
  "email": "user@example.com"
}

구조

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

  • JWT 표준에서 미리 정의된 클레임 이름.
  • 선택적으로 사용할 수 있음.
  • 대표적인 클레임:
iss (issuer): 토큰 발급자.
sub (subject): 토큰의 주제, 주로 사용자 ID.
aud (audience): 토큰의 대상(누가 사용할지).
exp (expiration): 토큰 만료 시간(유닉스 타임스탬프).
iat (issued at): 토큰 발급 시간.
nbf (not before): 이 시간 이후부터 유효한 토큰.

2. 공개 클레임(Public Claims)

  • 사용자 정의 클레임으로, 서비스에 필요한 데이터를 담음.
{
  "userId": "12345",
  "role": "admin",
  "email": "user@example.com"
}

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

  • 특정 서비스 간에만 사용되는 데이터.
  • 서비스 간 협의된 데이터 형식이어야 함.

특징

  1. Base64URL 인코딩됨, 그러나 암호화 된 것은 아니며, 디코딩하면 누구나 읽을 수 있다.
  2. 민감한 정보는 담지 않는다. (암호화되지 않으므로)
  3. 보안은 서명(Signature)로 처리된다.

3) signature

header + payload와 서버에 있는 key값을 header에서 정의한 알고리즘으로 암호화한것
이를 통해 데이터의 무결성과 신뢰성을 보장한다.

header와 payload는 제3자가 복호화 및 조작할 수 있지만 signature는 서버의 비밀키가 유출되지않는 이상 복호화 할 수 없다.

역할

데이터 무결성 보장:
Header와 Payload가 변경되지 않았음을 확인.
만약 누군가 Header나 Payload를 변경하면 서명이 유효하지 않게 된다.

발급자 신뢰 보장:
서명은 비공개키 또는 비밀키로 생성되기 때문에, 해당 키를 가진 서버만 JWT를 생성할 수 있어, 이를 통해 JWT를 신뢰할 수 있다.

생성 방법

1. Header와 Payload를 조합
Base64URL로 인코딩된 Header와 Payload를 .로 연결한다.

encodedHeader + "." + encodedPayload

2. 서명 알고리즘 적용

  • 위의 데이터를 서버가 가진 키(비밀키 또는 비공개키)로 서명.
  • 서명 방식은 JWT의 Header에 명시된 알고리즘(alg)을 사용한다.
    예: HS256, RS256

3. 서명 결과는 JWT의 세 번째 부분이 된다.

<encodedHeader>.<encodedPayload>.<signature>

검증 과정

  1. 클라이언트가 서버에 JWT를 보냄.
  2. 서버는 JWT의 Header와 Payload를 다시 조합.
  3. 자신의 키(비밀키 또는 공개키)로 서명을 검증:
  • 일치하면: 데이터가 변조되지 않았음을 확인.
  • 불일치하면: 토큰이 위조되었거나 유효하지 않음.

JWT는 언제 사용하는게 좋은가?

  1. 확장성이 중요할 때: JWT는 서버가 상태를 관리하지 않아도(stateless) 되기 때문에, 서버 간 부하 분산이 쉽다.
    예: 클러스터 환경에서 각 서버가 인증 데이터를 공유할 필요 없이 JWT로 인증 처리.

  2. 간단한 인증이 필요한 경우: 인증 과정이 단순하고, API 호출이 많은 서비스에 적합.
    예: 소규모 서비스, SPA(Single Page Application) 등.

  3. 권한 부여가 필요한 경우: JWT의 Payload에 권한(scope) 정보를 담아서 특정 리소스에 대한 접근을 제어.
    예: API 호출 시 사용자의 역할(Role)에 따라 다르게 처리.

JWT의 한계

  1. 유출되면 바로 위험: JWT는 소유자 중심(Bearer) 인증 방식이므로, 누군가 토큰을 탈취하면 해당 사용자처럼 행동할 수 있음.

  2. 서버에서 토큰 취소가 어렵다: JWT는 기본적으로 상태를 저장하지 않으므로, 유효기간이 남아 있는 동안은 무효화하기 어려움.
    해결책: 짧은 만료시간 + Refresh Token 사용.

    ➡️ 금융 서비스나 초고보안 환경에서는 JWT만으로 충분하지 않을 수 있으니, 다른 방식(mTLS, SAML 등)과 병행해야 할 수도 있다.

JWT의 보안 강화 방법

  1. HTTPS 사용: JWT는 Payload가 암호화되지 않으니, HTTPS로 전송해 탈취를 방지.

  2. 짧은 만료시간 설정 (exp): Access Token의 만료시간을 짧게 설정하고, Refresh Token으로 연장.

  3. 민감한 정보는 절대 Payload에 포함하지 않기: 비밀번호, 카드 정보 같은 민감 데이터를 넣지 말고, 서버에서 필요할 때만 조회.

  4. 비대칭키 사용 (RS256): 서버 간 신뢰 관계가 필요하다면, 비대칭키로 서명해 보안을 높임.

  5. Token Blacklist 관리: 토큰이 유출되거나 강제 로그아웃 시 사용 못 하게 서버에서 블랙리스트 관리.

Access Token과 Refresh Token의 사용에 관해서

stateless의 장점이 사라지는 것 아닌가?

Refresh Token의 탈취에 대비해서, Refresh Token은 서버에 저장되어 관리되어야한다. 이렇게 되면 완전한 stateless는 아니게된다. 하지만 Refresh Token을 저장해도, JWT 기반 인증이 주는 확장성과 장점을 유지하면서 보안성을 강화할 수 있다.

왜 Refresh Token을 서버에 저장해도 괜찮을까?

Access Token은 여전히 Stateless:
Access Token은 클라이언트에서 직접 관리하고, 서버는 이를 검증하기만 한다.
Access Token이 만료되기 전까지는 별도의 상태 관리 없이 인증이 가능.

보안 강화를 위해 필요:
Refresh Token을 서버에 저장하면 탈취되더라도 즉시 무효화할 수 있다. 무한히 Access Token을 발급받는 문제를 효과적으로 차단할 수 있다.

stateless의 장점을 유지하는 이유

확장성: Access Token 검증은 서버 상태를 참조하지 않아도 가능하기때문에, 분산 환경에서 Access Token은 여전히 유효하게 작동.

클라이언트 중심 인증: Access Token은 클라이언트에 저장되며, 요청마다 서버에 인증 상태를 조회하지 않아도 된다.

주요 인증 로직은 여전히 Stateless: Refresh Token 관리는 Access Token 만료 시에만 작동하므로, 주요 인증 흐름은 여전히 Stateless.

Refresh Token이 만료되면 로그인 시키기 vs Access Token을 발급할 때마다 Refresh Token도 같이 발급하기

Refresh Token이 만료되면 로그인 시키는 것이 좋은 경우

일반적인 웹/모바일 서비스: 보안 수준이 기본적인 SSL/TLS로 충분히 보장된다면 굳이 Refresh Token을 자주 갱신할 필요가 없다.서버 부하와 관리 복잡성만 증가한다.

서비스 확장성이 중요한 경우: 상태 저장이 많아지면, 확장성과 분산 처리가 어려워질 수 있다.

Access Token을 발급할 때마다 Refresh Token도 같이 발급하는 것이 좋은 경우

보안이 매우 중요한 서비스:
예: 금융 서비스, 의료 서비스.
Refresh Token 유출에 민감한 환경에서는 토큰을 자주 갱신하여 위험을 줄일 수 있다.

유출 가능성이 높거나 의심되는 환경:
예: 공용 네트워크나 디바이스에서 사용하는 서비스.

➡️ 민감한 정보를 다룬다면, 함께 발급해서 보안 강화를 추천하지만 일반적인 애플리케이션에서는 Refresh Token시 재인증을 시키는 방식으로도 충분하다.

참고 :
JWT (Json Web Token)의 구조와 사용하기

Access Token과 Refresh Token이란 무엇이고 왜 필요할까?

profile
프론트엔드 개발을 하고 있습니다 ⌨️

0개의 댓글