Skip to content

Conversation

@claire-lemon
Copy link
Contributor

KMS sign/verify 개선

lemon-core의 JWT 검증/서명 로직이 KMS 의존 없이도 동작하도록 확장했습니다. KMS 경로도 유지해 하위호환을 보장합니다.

시나리오

A(backend) -> token 발급

  1. 로그인/세션 발급 시 identityToken(jwt)를 생성해 응답 모델에 넣습니다.
  2. JWT 생성은 issueIdentityToken() -> encodeToken() -> signToken() 순서입니다.
  3. 서명 방식 선택은 환경변수 USE_KMS_SIGN(또는 옵션 useKms)으로 결정됩니다.
  4. KMS 모드면 iss = kms/{alias} + AWSKMSService.sign() 사용.
  5. non-KMS 모드면 iss = service + HMAC(HS256) 서명 사용.
  6. non-KMS secret은 MyHttpHeaderTool.buildJwtSecret() 호출값을 사용합니다.

lemon-core -> 공통 검증

  1. MyHttpHeaderTool.buildJwtSecret()이 기본 secret 생성의 단일 기준입니다.
  2. secret 생성식은 AWS_ACCOUNT_ID + JWT_MAGIC_KEY 기반 HMAC입니다.
  3. HTTP 컨텍스트 구성 시 parseIdentityHeader()로 JWT를 파싱/검증합니다.
  4. 검증 분기: iss가 kms/면 KMS verify, 그 외 문자열이면 HS256 verify.

B(service) -> token 사용/검증

  1. MyHttpHeaderTool을 상속해 동일 검증 로직을 사용합니다.
  2. 컨텍스트 생성 시 parseIdentityHeader()를 호출합니다.
  3. x-lemon-identity는 header 우선, 없으면 query parameter fallback을 사용합니다.
  4. AuthProxy.verifyToken()에서 A(backend) 검증 API를 호출해 재검증합니다(목표 SLA ~20ms).
  5. 재검증 성공 후 payload를 서비스 로직에서 사용합니다.

A(backend)와 lemon-core 영역의 KMS 대체(HS256 기반) 적용은 대부분 완료되어 현재 테스트 중입니다.
B(service) 영역은 4~5번(backend 재검증 API 연동, 재검증 결과 기반 payload 활용)은 순차 적용할 계획입니다.

환경변수 정리

  • JWT_MAGIC_KEY
    • required
    • lemon-core secret 생성에 사용.
  • USE_KMS_SIGN
    • optional(default 0)
    • A에서 KMS/HS 서명 선택 플래그.
  • MY_IDENTITY_KEY_ALIAS
    • optional
    • A에서 KMS alias 기본값.

const secret = params?.secret ?? MyHttpHeaderTool.buildJwtSecret();
// Verify HMAC-SHA256 signature manually
const message = [header, payload].join('.');
const expectedSig = fromBase64(crypto.createHmac('sha256', secret).update(message).digest('base64'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

crypto를 직접 쓰는것보다는 $U.md5() 같은것을 이용해줘요.

@steve-lemon
Copy link
Member

@aiden-lemon 도 리뷰 확인해줘요.

const HEADER_LEMON_LANGUAGE = $U.env('HEADER_LEMON_LANGUAGE', 'x-lemon-language');
const HEADER_LEMON_IDENTITY = $U.env('HEADER_LEMON_IDENTITY', 'x-lemon-identity');
const HEADER_COOKIE = $U.env('HEADER_COOKIE', 'cookie');
const JWT_MAGIC_KEY = $U.env('JWT_MAGIC_KEY', '');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM;
프로젝트에 공통적으로 JWT_MAGIC_KEY를 설정하여 이용하는 패턴이 맞을까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 맞습니다 사용 방법도 정리해서 전달 드리겠습니다!

@claire-lemon
Copy link
Contributor Author

claire-lemon commented Feb 13, 2026

KMS sign/verify 개선

주요 개선사항

1. MyHttpHeaderToolV2 클래스 추가

  • MyHttpHeaderTool을 상속하여 KMS 없이 HS256 기반 JWT 서명/검증 지원
  • reqContext에서 accountId를 직접 참조하여 secret 생성

2. encodeIdentityJWT 개선

  • 역할: 메세지 생성
  • exp 파라미터 추가: 만료시간을 직접 설정 가능 (기본값: 24시간)
  • iss 형식 변경: KMS 미사용 시 api/{service} 형식으로 issuer 설정

3. signToken 메서드 추가

  • 역할: 메세지 서명
  • HS256/KMS 서명을 분리

4. crypto3 유틸리티 추가

  • nonce + timestamp 기반 암호화/복호화
  • 시간 기반 만료 검증 (maxAge 옵션)
  • $U.encrypt() / $U.decrypt() shorthand 제공

사용 시나리오: 커스텀 exp로 토큰 발급

// 1. MyHttpHeaderToolV2 인스턴스 생성
const $tool = new MyHttpHeaderToolV2(headers, reqContext);

// 2. encodeIdentityJWT로 JWT 생성 (exp 직접 설정 가능)
const current = Date.now();
const expires = 60 * 60; // 1시간
const encoded = await $tool.encodeIdentityJWT(identity, {
    current,
    exp: Math.floor(current / 1000) + expires,
});

// 3. 토큰 사용
const accessToken = encoded.token;
// 또는 서명만 별도로 사용: encoded.signature

signToken 단독 사용

// 메시지에 직접 서명이 필요한 경우
const signature = await $tool.signToken('', encoded.token, { useKms: false });

시나리오

A(backend) -> token 발급

  1. 로그인/세션 발급 시 identityToken(JWT)을 생성
  2. JWT 생성: encodeIdentityJWT() + signToken() 호출
  3. 서명 방식: alias 유무로 결정
    • alias 있음 → iss = kms/{alias} + KMS 서명 (RS256)
    • alias 없음 → iss = api/{service} + HS256 서명
  4. HS256 secret은 buildJwtSecret() 호출값 사용

lemon-core -> 공통 검증

  1. MyHttpHeaderTool.buildJwtSecret()이 기본 secret 생성의 단일 기준입니다.
  2. secret 생성식은 AWS_ACCOUNT_ID + JWT_MAGIC_KEY 기반 HMAC입니다.
  3. HTTP 컨텍스트 구성 시 parseIdentityHeader()로 JWT를 파싱/검증합니다.
  4. 검증 분기: iss가 kms/면 KMS verify, 그 외 문자열(api/)이면 HS256 verify.

B(service) -> token 사용/검증

  1. MyHttpHeaderToolV2를 사용하여 동일 검증 로직 적용
  2. 컨텍스트 생성 시 parseIdentityHeader() 호출
  3. issapi/{service} 형식이면 자동으로 원격 검증 API 호출
    • $protocol.service.execute()POST /oauth/verify-token
    • 만약 자기자신이라면 verify 직접 수행
  4. 검증 성공 시 payload 반환, 실패 시 에러

A(backend)와 lemon-core 영역의 KMS 대체(HS256 기반) 적용은 대부분 완료되어 현재 테스트 중입니다.
B(service) 영역은 4~5번(backend 재검증 API 연동, 재검증 결과 기반 payload 활용)은 순차 적용할 계획입니다.

환경변수 정리

변수명 필수 설명
JWT_MAGIC_KEY required secret 생성에 사용

테스트 결과

chatic-backend-api localhost HTTP 테스트

테스트 전제

  • $web.toolsMyHttpHeaderToolV2로 교체한 상태에서 수행
  • 브랜치: feat/replace-kms

테스트 시나리오

시나리오 설명 결과
A 토큰 발급 (POST /oauth/0/verify-native-token) PASS
B 발급 토큰 검증 (POST /oauth/verify-token) PASS
C 변조 토큰 검증 실패 PASS (의도된 실패)

발급 JWT 확인값

  • alg: HS256
  • iss: api/chatic-backend-api
  • sid, uid, iat, exp: 정상 포함


// 1. basic encrypt/decrypt (like crypto2 pattern)
const $crypt = $U.crypto3(secret);
const encrypted = $crypt.encrypt(data);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

암호화된 결과도 보이주도록해줘요. (nonce, 와 current 고정시켜서)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants