How to Decrypt Spring Boot Properties with a Custom EnvironmentPostProcessor
This guide shows how to implement a custom EnvironmentPostProcessor in Spring Boot to decrypt sensitive configuration values such as datasource passwords, explains registration via META-INF/spring.factories, and details the execution order and related Spring Boot internals.
Introduction
Spring Boot does not provide built‑in encryption for property values, but it offers hook points such as
EnvironmentPostProcessorthat allow you to manipulate the
Environmentbefore the application starts. An alternative for secure credential storage is Spring Cloud Vault.
Custom Extension Point
Implement a custom
EnvironmentPostProcessorto decrypt encrypted properties (e.g., passwords) at startup.
<code>public class EncryptEnvironmentPostProcessor implements EnvironmentPostProcessor {
private static final String PWD_PREFIX = "enc(";
private static final String PWD_SUFFIX = ")";
private static final String DS_KEY = "spring.datasource.password";
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
System.out.println("准备处理加解密数据");
String originPassword = environment.getProperty(DS_KEY);
System.out.println("原始密码: " + originPassword);
if (originPassword != null && originPassword.startsWith(PWD_PREFIX) && originPassword.endsWith(PWD_SUFFIX)) {
String encryptPassword = originPassword.substring(originPassword.indexOf('(') + 1, originPassword.length() - 1);
String decryptPassword = decrypt(encryptPassword);
System.out.println("解密后的密码:" + decryptPassword);
Map<String, Object> kv = new HashMap<>(2);
kv.put(DS_KEY, decryptPassword);
PropertySource<Map<String, Object>> source = new MapPropertySource("ds-pwd", kv);
environment.getPropertySources().addFirst(source);
}
System.out.println("加解密数据处理完成");
}
private static String decrypt(String source) {
byte[] bs = source.getBytes(Charset.forName("UTF-8"));
for (int i = 0; i < bs.length; i++) {
bs[i] = (byte) (bs[i] ^ 0x01);
}
return new String(bs, Charset.forName("UTF-8"));
}
}
</code>Register the processor in
META-INF/spring.factories:
<code>org.springframework.boot.env.EnvironmentPostProcessor=com.pack.envs.EncryptEnvironmentPostProcessor
</code>Test Configuration
<code>spring:
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/testjpa?serverTimezone=GMT%2B8
username: root
password: enc(032032)
type: com.zaxxer.hikari.HikariDataSource
hikari:
minimumIdle: 10
maximumPoolSize: 200
autoCommit: true
idleTimeout: 30000
poolName: MasterDatabookHikariCP
maxLifetime: 1800000
connectionTimeout: 30000
connectionTestQuery: SELECT 1
</code>When the application starts, the custom processor runs before the IOC container is created, decrypting the password and injecting the clear value into the environment.
Source Code Analysis
Execution Entry Point
<code>public class SpringApplication {
public ConfigurableApplicationContext run(String... args) {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// Pre‑process Environment
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// Refresh context (initialize IOC container)
refreshContext(context);
return context;
}
// ... other methods including prepareEnvironment ...
}
</code>Event Handling
<code>public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
List<EnvironmentPostProcessor> loadPostProcessors() {
// Load processors configured in META-INF/spring.factories
return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader());
}
}
</code>The whole execution chain is illustrated below:
Ordering Note
ConfigFileApplicationListenerimplements
Orderedwith order
Ordered.HIGHEST_PRECEDENCE + 10. If a custom
EnvironmentPostProcessorimplements
Orderedwith a lower value (higher priority), it may run before the environment is fully prepared, causing
NullPointerExceptionbecause required properties are not yet available.
Configuration Locations
<code>private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/";
</code>These locations are scanned from right to left, with later entries having higher precedence.
End of tutorial.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.