Spring Cloud Config의 복호화 실패 처리 과정 — {cipher}...가 invalid.*로 바뀌는 이유
Spring Cloud Config Server를 쓰다 보면,
암호화된 설정값 {cipher}...가 복호화에 실패하면서 invalid.my.secret 형태로 노출되고,
결국 로컬 application.properties의 값이 적용되는 걸 본 적이 있을 겁니다.
오늘은 그 내부 메커니즘을 코어 코드 레벨에서까지 뜯어보려 합니다.
암호화된 설정이 서버에 전달되는 구조
Config Server는 Git, FileSystem, Vault 등에서 설정 파일을 읽고,
EnvironmentRepository.findOne()을 통해 클라이언트에 Environment 객체(JSON)를 반환합니다.
이때 설정값 중 {cipher}...로 시작하는 항목이 있으면
EnvironmentController → EnvironmentRepository → EnvironmentEncryptor.decrypt()
단계를 거치며 복호화를 시도합니다.
// EnvironmentEncryptorEnvironmentRepository.java
@Override
public Environment findOne(String name, String profiles, String label, boolean includeOrigin) {
Environment environment = this.delegate.findOne(name, profiles, label, includeOrigin);
if (this.environmentEncryptors != null) {
for (EnvironmentEncryptor environmentEncryptor : environmentEncryptors) {
environment = environmentEncryptor.decrypt(environment);
}
}
if (!this.overrides.isEmpty()) {
environment.addFirst(new PropertySource("overrides", getOverridesMap(includeOrigin)));
}
return environment;
}
기본적으로 아래 두 개의 EnvironmentEncryptor가 존재합니다.
CipherEnvironmentEncryptor(기본 RSA/Key 기반 복호화)VaultEnvironmentEncryptor(HashiCorp Vault 연동 시)
복호화 실패 시 invalid.* 키로 무효화 처리
CipherEnvironmentEncryptor.decrypt() 내부를 보면,
각 PropertySource의 키/값을 순회하며 {cipher} 접두어가 붙은 항목을 복호화합니다.
복호화 중 예외(EncryptionOperationNotPossibleException)가 발생하면 다음과 같이 처리됩니다.
// CipherEnvironmentEncryptor.decrypt() 코드 요약
catch (Exception e){
if(this.prefixInvalidProperties) {
value = "<n/a>";
name = "invalid." + name;
}
String message = "Cannot decrypt key: " + key + " (" + e.getClass() + ": " + e.getMessage() + ")";
}
map.put(name, value);
즉, 원래 키 my.secret 대신 invalid.my.secret이 추가됩니다.
이때 기존 my.secret 키는 존재하지 않으므로, Config Client는 이 값을 무시하게 됩니다.
클라이언트에서 invalid.* 가 무시되는 이유
Config Client가 서버로부터 받은 설정을 Environment로 머지할 때,
PropertySourcesPropertyResolver가 순서대로 PropertySource를 순회하며 값을 찾습니다.
- 먼저
configserver:*PropertySource를 탐색 {cipher}복호화 실패 →invalid.my.secret만 존재- 찾지 못하면 다음
PropertySource(로컬 파일 등)로 fall-through application.properties에서my.secret=localValue발견 → 반환 ✅
즉, invalid.* 키는 단지 실패 흔적일 뿐, Environment에서는 참조되지 않습니다.
이 동작은 AbstractEnvironment 내부에서 merge될 때, 단순히 PropertySource 순서대로 붙기 때문에
특별한 예외처리 없이 “뒤에 오는 PropertySource가 우선권을 가진다”는 일반 규칙으로 해결됩니다.
