게임 서버 Secret Rotation 무중단 구현: 세션 검증을 위한 이중 키 운영 방법

게임 서버 Secret Rotation에서 기존 세션이 갑자기 만료되는 원인과 해결법을 정리합니다. JWT·HMAC 키를 active/previous 이중 키로 운영하고 TTL, 시계 오차, 배포 전파 시간을 반영해 무중단 검증을 구현하는 실전 가이드입니다.

✓ 복사 완료!
챕터일반 가이드
난이도중급
상태● 검토 완료
대상 엔진
Engine Agnostic
최종 업데이트
게임 서버 Secret Rotation 무중단 구현: 세션 검증을 위한 이중 키 운영 방법

핵심 요약

Secret Rotation 시점에 새 키로 서명하고 기존 키로 서명된 세션도 일정 기간 검증해야 사용자 강제 로그아웃을 막을 수 있습니다. 검증기는 active와 previous 키를 모두 허용하되 서명에는 active 키만 사용합니다. 이중 키 기간은 토큰 TTL, 서버 시계 오차, 키 배포 지연 시간을 합산해 결정합니다.

게임 서버 Secret Rotation에서 세션 검증이 끊기는 이유는 무엇인가?

게임 서버는 로그인 세션, 매치 참가 토큰, WebSocket 연결 인증, 내부 서비스 간 요청 서명에 Secret을 사용합니다. 보안을 위해 Secret을 주기적으로 교체하는 Secret Rotation을 적용하지만 다음과 같은 방식은 기존 세션을 즉시 무효화합니다.

  1. 모든 서버가 새 Secret만 읽도록 변경한다.
  2. 클라이언트가 보낸 기존 토큰을 새 Secret으로 검증한다.
  3. 이전 Secret으로 서명된 토큰의 검증이 실패한다.

토큰의 서명 키가 바뀌면 토큰의 exp가 남아 있어도 서명 검증은 실패합니다. 결과적으로 플레이 중인 사용자가 로그인 화면으로 이동하거나 WebSocket 연결이 끊기고 매치 재접속도 실패할 수 있습니다.

이 문제의 핵심은 키 교체와 세션 만료가 서로 다른 생명주기를 가진다는 점입니다. 키는 교체할 수 있지만 기존 토큰은 설정된 TTL까지 유효해야 합니다.

이중 키 운영은 어떻게 동작하는가?

이중 키 운영은 키를 다음 두 상태로 관리하는 방식입니다.

키 상태서명 생성토큰 검증일반적인 역할
active허용허용현재 발급되는 토큰의 서명
previous금지허용Rotation 전에 발급된 토큰의 검증
retired금지금지보관 또는 폐기된 키

Rotation 직후에도 검증기는 두 키를 모두 알고 있어야 합니다. 반면 새로 발급하는 토큰은 항상 active 키만 사용해야 시간이 지날수록 이전 키로 서명된 토큰이 자연스럽게 줄어듭니다.

토큰 헤더에는 키 버전을 식별할 수 있는 kid를 넣습니다. 검증기는 kid를 이용해 모든 키를 순회하는 대신 필요한 키를 바로 선택할 수 있습니다.

{
  "alg": "HS256",
  "kid": "session-key-2026-08"
}

세션 토큰의 최소 클레임은 다음처럼 구성할 수 있습니다.

{
  "sub": "player-123",
  "sid": "session-456",
  "iat": 1787529600,
  "exp": 1787533200,
  "iss": "game-auth",
  "aud": "game-server",
  "kid": "session-key-2026-08"
}

sid는 서버 측 세션 저장소의 세션 ID입니다. JWT 서명만으로 모든 세션 제어를 처리하지 않고 sid를 데이터베이스나 캐시에서 조회하면 강제 로그아웃과 세션 폐기를 별도로 처리할 수 있습니다.

이중 키 기간은 어떻게 계산해야 하는가?

이중 키 기간은 이전 키로 서명된 토큰이 정상적으로 만료될 때까지 유지해야 합니다. 최소 기준은 다음과 같습니다.

Toverlap≥Ttoken TTL+Tclock skew+Tdistribution delay+Tsafety marginT_{\mathrm{overlap}} \ge T_{\mathrm{token\ TTL}} + T_{\mathrm{clock\ skew}} + T_{\mathrm{distribution\ delay}} + T_{\mathrm{safety\ margin}}

예를 들어 다음 조건이라면 이중 키 기간은 최소 70분으로 잡는 것이 안전합니다.

항목값
액세스 토큰 TTL60분
허용하는 서버 시계 오차30초
키 배포 및 캐시 반영 지연5분
운영 안전 여유5분
권장 이중 키 기간70분 이상

여기서 distribution delay에는 Secret Manager 또는 Vault에서의 조회 지연뿐 아니라 다음 요소도 포함해야 합니다.

  • 게임 서버 프로세스의 설정 캐시 갱신 주기
  • Kubernetes Secret 또는 환경 변수 반영 시간
  • 리전 간 복제 지연
  • 오토스케일링으로 새로 생성된 인스턴스가 키를 읽는 시간
  • CDN이나 인증 게이트웨이의 검증 키 캐시 TTL

Refresh Token도 같은 키로 검증한다면 Refresh Token TTL을 기준으로 계산해야 합니다. Refresh Token이 30일 동안 유효한데 이중 키 기간을 액세스 토큰 TTL인 1시간만 유지하면 Rotation 후 Refresh 요청이 실패합니다. 액세스 토큰과 Refresh Token에 서로 다른 키 그룹을 사용하거나 Refresh Token을 서버 측 저장 방식으로 운영하는 방법이 좋습니다.

Secret Rotation 절차를 1, 2, 3단계로 구현하려면?

1단계: 새 키를 먼저 배포한다

모든 인증 서버와 게임 서버에 새 키를 active 후보로 배포합니다. 이때 기존 키도 previous로 함께 배포해야 합니다. 아직 서명 키를 바꾸지 않고 검증 가능한 키 목록을 먼저 확장하는 것이 중요합니다.

keyring:
  - kid: session-key-2026-08
    status: active
  - kid: session-key-2026-07
    status: previous

키 값 자체는 소스 코드나 이미지에 저장하지 말고 AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager와 같은 Secret 저장소에서 읽습니다. 로그에는 Secret 값이 절대 출력되지 않도록 하고 kid만 기록합니다.

2단계: 서명 키를 새 키로 전환한다

키링이 모든 인스턴스에 반영된 것을 확인한 뒤 새 키를 서명용 active 키로 지정합니다. 이후 로그인, 토큰 갱신, 매치 재접속 토큰 발급은 새 키로만 처리합니다.

interface KeyVersion {
  kid: string;
  secret: Uint8Array;
  status: "active" | "previous";
  notBefore: number;
  notAfter?: number;
}

function verifySession(token: string, keyring: KeyVersion[], now: number) {
  const header = decodeJwtHeader(token);
  const key = keyring.find(item => item.kid === header.kid);

  if (!key) return { ok: false, reason: "unknown_kid" };
  if (now < key.notBefore) return { ok: false, reason: "key_not_yet_valid" };
  if (key.notAfter !== undefined && now >= key.notAfter) {
    return { ok: false, reason: "key_retired" };
  }
  if (key.status !== "active" && key.status !== "previous") {
    return { ok: false, reason: "key_not_accepted" };
  }

  const claims = verifySignatureAndClaims(token, key.secret, {
    algorithm: "HS256",
    issuer: "game-auth",
    audience: "game-server",
    clockToleranceSeconds: 30
  });

  if (!claims.ok) return { ok: false, reason: claims.reason };
  return { ok: true, claims: claims.value };
}

실제 라이브러리를 사용할 때도 알고리즘을 토큰의 alg 값에 맞춰 임의로 선택하지 말고 서버 설정으로 고정해야 합니다. HMAC 방식이면 HS256처럼 허용 알고리즘을 제한하고 비대칭 키 방식이면 RS256 또는 EdDSA 등 서비스에서 정한 알고리즘만 허용합니다.

3단계: 이전 키를 폐기한다

이전 키의 notAfter가 지나고 모니터링에서 이전 키 사용량이 0에 가까워지면 previous 키를 retired로 전환합니다. 즉시 삭제하기보다는 Secret 저장소의 버전 관리 기능을 활용해 복구 가능한 상태로 보관하는 편이 운영에 유리합니다.

Rotation 시작
  ↓
새 키 배포 + 이전 키 검증 허용
  ↓
새 키로 서명 전환
  ↓
토큰 TTL과 전파 지연만큼 대기
  ↓
이전 키 검증 차단
  ↓
이전 키 보관 또는 폐기

운영 자동화에서는 단순히 날짜만 기준으로 삼지 말고 kid별 검증 실패율과 사용량을 함께 확인해야 합니다. 일부 리전이나 오래 실행된 서버가 키를 갱신하지 못하면 예상보다 긴 시간 동안 이전 키 토큰이 발생할 수 있습니다.

게임 세션 검증에서 키 교체와 세션 폐기를 어떻게 분리할까?

Secret Rotation은 키의 기밀성을 유지하기 위한 절차이고 세션 폐기는 특정 세션이나 계정을 차단하는 절차입니다. 두 작업을 하나로 묶으면 정상적인 Rotation도 전체 사용자 로그아웃으로 이어질 수 있습니다.

권장 검증 순서는 다음과 같습니다.

  1. 토큰 형식과 알고리즘을 확인한다.
  2. kid로 active 또는 previous 키를 선택한다.
  3. 서명과 exp, nbf, iss, aud를 검증한다.
  4. sid가 세션 저장소에 존재하는지 조회한다.
  5. 세션 상태가 revoked가 아닌지 확인한다.
  6. 게임 계정의 밴 상태와 매치 참가 권한을 확인한다.

이 구조라면 특정 사용자의 세션만 폐기하면서 전체 키는 계속 사용할 수 있습니다. 반대로 Secret이 유출된 긴급 상황에서는 이전 키를 즉시 검증 목록에서 제거해야 합니다. 이 경우 기존 세션이 끊길 수 있으므로 정상 Rotation과 긴급 폐기를 운영 절차와 알림에서 구분해야 합니다.

멀티 리전 게임 서버에서는 무엇을 추가로 확인해야 하는가?

멀티 리전 환경에서는 키 전환 시점이 모든 서버에서 동일하지 않을 수 있습니다. 따라서 다음 조건을 지켜야 합니다.

  • 키 메타데이터에는 kid, notBefore, notAfter를 포함한다.
  • 모든 리전이 동일한 키 버전 목록을 읽도록 한다.
  • 인스턴스 시작 시 키링이 최소 두 개의 유효한 버전을 포함하는지 검사한다.
  • 키 캐시에는 TTL을 설정하고 수동 즉시 갱신 기능을 제공한다.
  • Rotation 전후에 unknown_kid, key_retired, signature_invalid 비율을 비교한다.
  • 서버 시계는 NTP 또는 클라우드 시간 동기화 기능으로 관리한다.

비대칭 키를 사용하는 경우에는 검증 서버가 공개 키 목록을 JWKS 형태로 읽도록 구성할 수 있습니다. 공개 키는 노출되어도 되지만 JWKS 캐시가 너무 길면 새 키를 모르는 서버가 생기므로 Cache-Control과 강제 갱신 정책을 함께 설계해야 합니다.

Secret Rotation을 테스트하는 방법은 무엇인가?

운영 전에 다음 시나리오를 자동화 테스트로 검증해야 합니다.

테스트 시나리오기대 결과
Rotation 전 토큰을 previous 키로 검증성공
Rotation 후 발급 토큰을 active 키로 검증성공
이전 키의 notAfter 이후 토큰 검증실패
존재하지 않는 kid 사용실패
만료된 토큰 사용실패
서버 시계가 허용 범위를 초과실패
일부 인스턴스가 새 키를 늦게 수신정상 검증 또는 명확한 알림
세션 저장소에서 sid가 폐기됨실패

특히 실제 배포와 비슷하게 다음 순서를 재현해야 합니다.

  1. 일부 인스턴스에만 새 키를 배포한다.
  2. active 키를 새 버전으로 변경한다.
  3. 이전 인스턴스와 새 인스턴스 사이에서 로그인과 재접속을 반복한다.
  4. 키 캐시 만료 전후의 검증 결과를 확인한다.
  5. previous 키 폐기 후 기대한 토큰만 실패하는지 확인한다.

모니터링 지표는 전체 인증 실패율만으로는 부족합니다. kid별 검증 요청 수, 키별 성공·실패율, unknown kid 수, 키 로딩 실패 수, 세션 재접속 실패율을 분리해 기록해야 원인을 빠르게 찾을 수 있습니다.

자주 묻는 질문(FAQ)

이전 키를 얼마나 오래 보관해야 하나요?

검증 허용 기간은 해당 키로 발급된 토큰의 최대 TTL에 시계 오차와 배포 지연을 더한 시간 이상이어야 합니다. Refresh Token까지 같은 키를 사용한다면 Refresh Token TTL을 기준으로 계산합니다.

active와 previous 키를 모두 사용하면 보안이 약해지지 않나요?

previous 키를 무기한 허용하면 위험하지만 정해진 notAfter까지 제한적으로 허용하는 것은 정상적인 Rotation 방식입니다. 키 유출이 의심되면 previous 키를 즉시 폐기하고 세션 재인증 절차를 실행해야 합니다.

JWT의 kid만 믿고 검증해도 되나요?

아닙니다. kid는 키를 찾기 위한 식별자일 뿐입니다. 서버가 허용한 키 목록과 대조하고 알고리즘, 발급자, 대상, 만료 시간, 세션 상태까지 함께 검증해야 합니다.

정리

게임 서버의 Secret Rotation은 새 키를 배포하는 작업만으로 끝나지 않습니다. 기존 토큰의 유효 기간을 보장하려면 active 키로 새 토큰을 발급하면서 previous 키로 기존 토큰을 검증하는 이중 키 기간이 필요합니다.

핵심은 세 가지입니다.

  • kid로 키 버전을 식별하고 서명은 active 키로만 수행한다.
  • 이중 키 기간을 토큰 TTL, 시계 오차, 키 배포 지연을 기준으로 계산한다.
  • Secret Rotation과 세션 폐기를 분리하고 키별 검증 지표를 모니터링한다.

이 구조를 적용하면 정기적인 키 교체가 게임 플레이 중인 사용자와 매치 재접속 흐름을 불필요하게 끊는 문제를 줄일 수 있습니다.