← 기술 지식

기술 지식 / 보안 / 세션·자격 관리

서명키·API 키의 중첩 교체와 긴급 회수

검증자 준비·발급 전환·옛 키 종료를 나누고 JWKS singleflight·미발견 캐시·유출 대응·실제 사용률로 완료를 판단합니다.

보안 · 원리·구현·운영
기술 지식 트리

키 교체는 새 비밀을 만드는 단일 배포가 아니라, 검증자가 새 키를 인지한 뒤 발급을 전환하고 옛 자격의 허용 창을 닫는 상태 전이입니다. 정상 교체와 유출 회수는 가용성과 공격 차단의 목표가 다르므로 같은 rollback 절차를 쓰면 안 됩니다.

새 키 배포와 검증자 인지 지연

발급자 한 대가 새 키 K2로 JWT를 만들기 시작했는데 검증 서비스 세 대 중 하나는 K1만 캐시하고 있다고 합시다.

상태를 K2 공개키 배포→모든 검증 경로 K2 확인→K2 발급 시작→K1 마지막 발급 시각 기록→최대 exp+clock skew 경과→K1 검증 거절로 남깁니다. K2 발급 전환 시각만 기록하면 오래된 worker나 배치가 K1을 계속 발급한 사실을 놓칠 수 있습니다. 유출이면 이 대기 창을 따르지 않고 K1 신뢰 제거와 관련 refresh/session 회수를 우선하며, 안전한 정기 교체의 rollback을 그대로 재사용하지 않습니다. 토큰은 정상적으로 발급됐지만 요청이 어느 서버로 가느냐에 따라 로그인이 실패합니다. 키 교체는 난수를 새로 만드는 일이 아니라 서로 다른 버전이 공존하는 기간을 관리하는 일입니다.

정상 정기 교체와 유출 키의 긴급 회수는 목표가 다릅니다. 정기 교체는 구·신 키 중첩으로 가용성을 유지할 수 있지만, 공격자가 가진 키를 계속 허용하면 긴급 회수 목적이 무너집니다.

검증 가능 상태와 발급 전환 순서

단계 발급자 검증자 종료 조건
준비 K1 사용 K1·K2 검증 가능 모든 경로에서 K2 확인
전환 K2로 새 발급 K1·K2 제한적 공존 K1 신규 발급 종료
대기 K2 사용 남은 K1 토큰 허용 마지막 K1 발급의 최대 유효 수명 종료
제거 K2 사용 K1 거절 오래된 자격 실제 거절 확인

K1 제거 시각은 계획상 전환 시각이 아니라 마지막 실제 K1 발급과 검증기가 허용하는 최대 토큰 수명·시계 오차를 기준으로 봅니다. 일부 배치가 K1을 계속 사용하면 대기 시작 자체가 늦어집니다. 검증 캐시의 폐기 시점도 관리해야 합니다. 토큰이 자의적으로 아주 긴 exp를 제시해도 검증 정책이 허용하는 수명 범위를 넘지 않도록 계약을 정합니다.

정상 키 교체의 안전한 순서K2 검증키 먼저 배포. K2로 발급 전환. K1 발급·사용 잔여 확인. K1 검증 허용 종료검증자 준비 확인중첩 기간허용 수명 종료K2 검증키 먼저 배포K2로 발급 전환K1 발급·사용 잔여 확인K1 검증 허용 종료
정상 키 교체의 안전한 순서 화살표는 배포 순서입니다. 공개 검증키 배포와 비밀 서명키 접근은 다른 경로이며, 이전 키 제거는 새 키 생성 직후가 아니라 옛 자격의 허용 창 종료 뒤입니다.
그림을 글로 읽기
  • K2 검증키 먼저 배포
  • K2로 발급 전환
  • K1 발급·사용 잔여 확인
  • K1 검증 허용 종료
  1. K2 검증키 먼저 배포 → K2로 발급 전환: 검증자 준비 확인
  2. K2로 발급 전환 → K1 발급·사용 잔여 확인: 중첩 기간
  3. K1 발급·사용 잔여 확인 → K1 검증 허용 종료: 허용 수명 종료

대칭 API 키는 상대 서버가 두 키를 동시에 허용할 수 있는지 먼저 확인합니다. 지원하면 새 키를 양쪽에 준비하고 실제 인증 호출을 확인한 뒤 모든 소비자·배치·연결 풀을 전환합니다. 하나만 허용한다면 교체 창과 제한된 호출 중단 등 다른 운영 절차가 필요합니다. JWT 공개키 배포 방식과 같다고 가정하지 않습니다.

알 수 없는 kid와 외부 요청 폭주 방지

kid(key ID)는 토큰의 서명을 검증할 키를 신뢰한 발급자 설정이 허용한 키 집합에서 찾기 위한 식별자입니다. 따라서 토큰이 보낸 임의 URL이나 파일명으로 키 위치를 만들지 말고, 먼저 해당 발급자의 캐시에서 kid를 찾습니다. 캐시에서 빠졌다면 정상 키 회전일 수도 있으므로 정해진 예산 안에서 갱신하되, 요청마다 외부 조회를 새로 만들면 인증 장애가 한꺼번에 커질 수 있습니다.

key = trusted_cache.find(kid)
if key is absent:
    if issuer_refresh_budget_allows and not short_negative_cache_hit(kid):
        await one_shared_refresh_for_this_issuer_with_deadline()
    key = trusted_cache.find(kid)
if key is absent:
    record_bounded_negative_result(kid)
    reject_unknown_key
verify_with_configured_algorithm_and_claim_policy(key)

코드의 one_shared_refresh_for_this_issuer_with_deadline()는 같은 발급자에서 동시에 캐시가 빗나간 요청을 한 번의 조회로 합치는 singleflight입니다. 서버 인스턴스가 여러 개면 각 인스턴스가 한 번씩 조회할 수 있으므로 인스턴스별 backoff·jitter·최소 갱신 간격과 전체 조회 예산을 함께 둡니다.

서로 다른 임의 kid가 많이 오면 negative cache의 항목 수와 메모리도 제한하고, 미발견 캐시의 수명을 지나치게 길게 잡아 정상 K2를 계속 거절하지 않도록 공개키 선배포와 짧은 미발견 캐시를 함께 운영합니다.

키 응답의 출처·크기·형식·타입을 검증한 후 새 snapshot으로 게시합니다. 갱신 실패가 기존의 유효한 키 집합을 빈 값으로 덮지 않게 하고, 오래된 키의 허용 기한·회수 정책은 그대로 강제합니다. 조회 장애를 서명 검증 생략으로 바꾸면 안 됩니다.

유출 대응과 키 중첩 기간의 한계

K1의 비밀키가 유출됐다면 공격자는 K1 서명을 새로 만들 수 있습니다. 정상 토큰 만료를 기다리는 것만으로는 부족하며 검증자에서 K1 신뢰를 제거하고 필요한 세션·refresh 계열을 회수합니다. 기존 연결과 캐시가 옛 신뢰를 얼마나 유지하는지도 확인합니다.

장애가 생겼다고 유출된 K1을 다시 켜는 롤백은 안전하지 않습니다. 새 대체 키나 유출되지 않은 독립 자격으로 복구합니다. 정상 교체에서는 안전한 이전 키를 제한적으로 되돌리는 선택이 가능해도 유출 대응에 그대로 적용하지 않습니다. 비밀 원문 대신 kid·버전·발급자·마지막 사용·검증 실패 이유로 영향을 조사합니다.

키 교체 완료의 사용·거절 경로 확인

가짜 발급자와 검증자 여러 개로 K2 미배포 서버, 느린 JWKS, 알 수 없는 kid, 부분 배포를 시험합니다. 동일 발급자의 조회 수가 제한되는지, 실패 때 정상 K1 검증 정책이 유지되는지, 종료된 K1은 캐시 hit에서도 거절되는지 봅니다.

실제 배포에서는 온라인 요청뿐 아니라 주간 배치·오래된 worker·장기 연결의 키 버전을 관찰해야 합니다. 설정 반영 성공과 실제 새 키 인증 성공을 분리하고, 옛 키 사용률이 남아 있으면 완료로 보지 않습니다. 이 노트는 교체 절차이며 실제 서비스 키를 발급·회수한 기록은 아닙니다.

후속·관련 개념

이 개념의 확장

함께 읽을 문서

분야별 학습 경로

전체 기술 지식
선택 연습 · 관련 질문 2개

질문은 이해를 확인하는 보조 자료입니다. 학습 경로와 문서의 범위는 질문 수와 독립적입니다.