HsmManager
- PKCS11_CONFIG_SECONDARY / PIN_SECONDARY 프로퍼티 추가.
PIN_SECONDARY 미설정 시 PIN 재사용, PKCS11_CONFIG_SECONDARY 미설정 시
후보가 1건이므로 기존과 동일하게 동작한다.
- 기동/재연결 모두 PRIMARY -> SECONDARY 순으로 시도해 최초 성공분을 사용한다.
연결 성공 판정은 Provider 생성 + KeyStore.load + alias/getKey probe까지 포함하며,
Security 등록 전에 검증하므로 실패해도 사용 중인 Provider/keyStore는 영향이 없다.
- 주기적 재로드에서 SECONDARY로 동작 중이면 PRIMARY 재연결을 먼저 시도해
PRIMARY 복구 시 자동 복귀한다. PRIMARY 정상 동작 중에는 기존 keyStore 인스턴스에
다시 load() 하여 PKCS11 세션 누적 방지 로직을 유지한다.
- PropertyChangeListener로 등록하여 HSM 프로퍼티 변경 시 다음 스케줄을 기다리지 않고
즉시 재적용한다. HSM 통신이 호출 스레드를 막지 않도록 스케줄러 스레드에 위임하며,
RELOAD_INTERVAL_MINUTES 변경 시 스케줄을 재등록한다.
- activeConfigName, failoverCount, lastReloadResult 등 모니터링 상태값을 노출한다.
HsmCryptoService
- 캐시 진단용 getCachedKeyInfos() / getCacheTtlMs() 추가. 키 원본은 노출하지 않는다.
/manage/hsm
- status(연결 설정/프로퍼티/alias/캐시 키), properties, keystore/aliases, cache/keys 조회와
reload, cache/clear 실행 API 추가. PIN 류 프로퍼티는 마스킹한다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013ZMr6y9kc6Y6TWN3x4gpYg
상대 시스템이 0 패딩된 코드값을 따옴표 없이(=JSON 숫자로) 보내면 표준 파서가
"Invalid numeric value: Leading zeroes not allowed" 로 전문 전체를 거부한다.
전문을 못 읽고 실패하는 것보다 값을 받아들이는 쪽이 낫다고 판단해
ALLOW_LEADING_ZEROS_FOR_NUMBERS 를 켠다.
newNumberSafeMapper() 한 곳에 넣어 이를 쓰는 전 경로(JsonReader 표준전문,
JacksonUtil.OBJECT_MAPPER 를 타는 어댑터/필터)를 함께 덮는다.
WRITE_BIGDECIMAL_AS_PLAIN 을 여기 넣었던 것과 같은 이유다.
주의: 이 옵션은 00001 을 숫자 1 로 만든다(선행 0 미보존).
항목이 NUMBER/LL_NUMBER 로 선언돼 있으면 StandardItem.toTypeValue() 가
어차피 선행 0 을 깎으므로 결과가 같고, STRING/ZZ_STRING 선언이면 자릿수가
사라진다. 후자는 상대에게 따옴표를 붙여 보내달라고 요청하는 것이 정답이다.
Jackson 2.12.7 실측: 선행 0 파싱 8종, 숫자 정밀도 왕복 5종, 제어문자 정규화
조합, 과허용 여부(+1 / .5 / 무따옴표키 / 홑따옴표는 여전히 거부) 19건 PASS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Phh6MSGvgHM3rPgGUU3dzR
수신측 - JacksonUtil.escapeControlChars() 적용 범위 확대
JsonReader 에만 걸려 있어 TemplateCodeConvertAdapterErrorMsgHandler 등
직접 readTree() 하는 경로가 "Illegal unquoted character ((CTRL-CHAR, code 10))"
로 실패했다. JacksonUtil.readTree() 공용 진입점과 payload 를 파싱하는
핸들러/필터에 적용한다.
송신측 - MessageUtil.stripControlChars() 신규
TemplateAdapterErrorMsgHandler.render() 가 Matcher.quoteReplacement() 로만
치환하고 있었다. 이것은 정규식 치환용이라 JSON/XML 이스케이프가 아니어서,
값에 개행이 있으면 깨진 JSON 을 만들어 응답으로 내보냈다. 실제로 위 파싱
오류의 원문도 상대 전문이 아니라 이 렌더 결과였다.
템플릿이 JSON/XML 어느 쪽인지 알 수 없어 포맷별 이스케이프가 불가능하므로
제어문자를 걸러낸다. 탭/개행/CR 은 공백으로, 나머지 제어문자와 DEL 은 제거.
render() 의 스칼라 치환과 renderRow() 의 행 필드 치환에만 적용해
템플릿 서식(들여쓰기/개행)은 보존한다.
검증: gradlew compileJava BUILD SUCCESSFUL.
databind 2.12.7 런타임에서 0x00~0x1F 전수 + 로그 재현 케이스 통과.
상대 시스템이 개행을 이스케이프하지 않고 그대로 보내는 경우
"Illegal unquoted character ((CTRL-CHAR, code 10))" 로 파싱이 실패했다.
파서 옵션(ALLOW_UNESCAPED_CONTROL_CHARS)으로 푸는 대신, 입력을 표준 JSON 으로
정규화하는 JacksonUtil.escapeControlChars() 를 추가하고 파서는 strict 로 유지한다.
문자열 리터럴 "안" 의 제어문자만 바꾼다. JSON 은 토큰 사이의 개행/탭을 공백으로
허용하므로 구조적 공백까지 치환하면 pretty-print 된 JSON 이 오히려 깨진다.
- EAIServiceMonitor: timeOutCodes.indexOf 판정을 '> 0' -> '>= 0' 으로 수정
(목록 첫 번째 코드가 타임아웃이 아닌 시스템에러로 분류되던 문제)
- EAIServiceMonitor: getLogRspErrCd() null 대비 defaultString 적용, 리터럴 비교 순서 변경
- EAILogDAO, EAIFileLogger: 응답에러코드 substring(1,2) 를 널 안전한 StringUtils.substring 으로 교체
JacksonUtil.newNumberSafeMapper() 는 USE_BIG_DECIMAL_FOR_FLOATS +
withExactBigDecimals 로 입력(파싱)측만 막고 있었다. DecimalNode 직렬화는
BigDecimal.toString() 이라 scale 이 음수이거나 adjusted exponent < -6 일 때
지수 표기가 나온다. 즉 파싱을 제대로 해도 내보낼 때 작은 소수가 깨졌다.
0.00000012 -> 1.2E-7 , -0.0000005 -> -5E-7
WRITE_BIGDECIMAL_AS_PLAIN 을 추가해 출력측까지 보존한다.
(금액처럼 scale >= 0 인 큰 값은 원래 영향 없음. 이율/환율이 대상)
또한 JacksonUtil.setValue() 가 ArrayNode.set(int, String) 을 쓰고 있었다.
이 오버로드는 jackson-databind 2.13 부터이고, 실제 런타임은 2.12.7 이다
(2.12.7 은 set(int, JsonNode) 하나뿐). jackson-dataformat-xml:2.13.1 이
compileOnly 로 컴파일 클래스패스에만 2.13.1 을 올려 컴파일만 통과하던
상태였고, 배열 경로에 setText() 하면 운영에서 NoSuchMethodError 가 났다.
TextNode.valueOf() 로 감싸 set(int, JsonNode) 에 바인딩한다.
그 외
- TemplateCodeConvertAdapterErrorMsgHandler: 응답 JSON 왕복 지점 전환
- HttpAdapterServiceSupport, HttpClient5AdapterServiceRestAddFilter: 직렬화 지점 전환
- JsonReader: parse() 호출마다 ObjectMapper 를 새로 만들던 것을 static 싱글턴으로
바꾸고 중복 설정 코드를 JacksonUtil 에 위임
- MessageKeyExtractor: 선언만 하고 쓰지 않는 ObjectMapper 필드 제거
- build.gradle: 미사용 jackson-dataformat-xml 제거 (common 은 Ignite 경유로 jackson 확보)
검증: gradlew compileJava BUILD SUCCESSFUL, databind 2.12.7 런타임에서
숫자 왕복 4건 + setText 배열/객체/미존재 경로 5건 전부 통과.
/manage/crypto 하위에 등록된 암호화모듈 목록/단건 조회, DYNAMIC 키
전략(KeyDerivationStrategy) 로드 상태 확인, 동적 키 캐시 키 목록 조회,
Base64 기반 암복호화 라운드트립 테스트 엔드포인트를 제공한다.
원본 암호키(encKeyHex/decKeyHex/ivHex)는 응답에 포함하지 않는다.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-인바운드/아웃바운드/내부 오류 발생 시 어댑터별로 프로퍼티에 등록된 템플릿을 사용하여
커스텀 에러 응답 메시지를 생성하는 TemplateAdapterErrorMsgHandler 구현 및 연동 작업.
- ExceptionHandler 변경
인바운드 어댑터에 ERR_MSG_HANDLER 프로퍼티가 설정된 경우 우선 실행
- ApiController에서 사용할 수 있는
generateNonStandardInboundErrorResponseMessage 추가, Controller에서도
Adapter가 식별되면 설정된 핸들러에 의해 에러메시지 행성 가능
- TxSiftContext 클래스 추가: SIFT MDC 등록/해제 및 appender close 공통 처리
- begin(): isSiftEnabled() 확인 후 MDC 등록, 이미 설정된 경우 스킵
- end(): appender 즉시 close 후 MDC 제거 (UUID 등 1회성 key 용도)
- HttpAdapterServiceSupport: MDC 직접 제어 코드를 TxSiftContext.begin/end로
교체