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 EnvironmentPostProcessor that allow you to manipulate the Environment before the application starts. An alternative for secure credential storage is Spring Cloud Vault.
Custom Extension Point
Implement a custom EnvironmentPostProcessor to decrypt encrypted properties (e.g., passwords) at startup.
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"));
}
}Register the processor in META-INF/spring.factories:
org.springframework.boot.env.EnvironmentPostProcessor=com.pack.envs.EncryptEnvironmentPostProcessorTest Configuration
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 1When 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
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 ...
}Event Handling
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());
}
}The whole execution chain is illustrated below:
Ordering Note
ConfigFileApplicationListenerimplements Ordered with order Ordered.HIGHEST_PRECEDENCE + 10. If a custom EnvironmentPostProcessor implements Ordered with a lower value (higher priority), it may run before the environment is fully prepared, causing NullPointerException because required properties are not yet available.
Configuration Locations
private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/";These locations are scanned from right to left, with later entries having higher precedence.
End of tutorial.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
