How to decrypt a property value when using PropertySourcesPlaceholderConfigurer - java

I have a properties file containing values like jdbc.password={enc}laksksjdjdj
Using JDK 1.7 and Spring 4.1.5 my configuration class looks like this
#PropertySources({
#PropertySource("classpath:application.properties"),
#PropertySource("classpath:env.properties")
})
#ComponentScan("com.acme")
#Configuration
public class SpringConfig
{
#Autowired
private ConfigurableEnvironment env;
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer()
{
return new EncryptedPropertySourcesPlaceholderConfigurer();
}
}
What I am trying to achieve is to translate any values in my properties file from an encrypted value to the actual value. Some values will be encrypted and some won't. This is what I have attempted so far and when I place a breakpoint on convertProperties() the props argument is always empty. I can't make any sense of this because I can see that at this point this.environment is loaded with all the properties from the files.
public class EncryptedPropertySourcesPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer
{
#Override
protected void convertProperties(Properties props)
{
Enumeration<?> propertyNames = props.propertyNames();
while (propertyNames.hasMoreElements()) {
String propertyName = (String) propertyNames.nextElement();
String propertyValue = props.getProperty(propertyName);
// String convertedValue = <translate the value>;
props.setProperty(propertyName, convertedValue);
}
}
#Override
protected Properties mergeProperties() throws IOException {
final Properties mergedProperties = super.mergeProperties();
convertProperties(mergedProperties);
return mergedProperties;
}
}
Has anyone been able to achieve this using PropertySourcesPlaceholderConfigurer? I have similar logic working in older applications using PropertyPlaceholderConfigurer but wanted to use the newer Spring configuration.
I noticed that Jasypt has a similar EncryptablePropertySourcesPlaceholderConfigurer but this behaves in the same fashion, so I'm confused.

For some reason using the #PropertySources annotation(s) does not work as expected.
When EncryptedPropertySourcesPlaceholderConfigurer.convertProperties() is called the autowired ConfigurableEnvironment does not contain the entries from my properties files. There are no references to my listed properties files at all.
To get around this limitation I removed the annotations and explicitly loaded these resources in in the config class. So it now reads like this
#ComponentScan("com.acme")
#Configuration
public class SpringConfig
{
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer()
{
EncryptedPropertySourcesPlaceholderConfigurer p = new EncryptedPropertySourcesPlaceholderConfigurer(new KeyfileDecryptor());
p.setLocations(
new ClassPathResource("application.properties"),
new ClassPathResource("env.properties")
);
return p;
}
#Bean
public DataSource dataSource(
#Value("${jdbc.driverclassname}") String driverclassname,
#Value("${jdbc.url}") String url,
#Value("${jdbc.username}") String username,
#Value("${jdbc.password}") String password)
{
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverclassname);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
This change introduced the issue that the autowired ConfigurableEnvironment will be null so I had to change to using #Value injection in order to configure my dataSource from the properties.
Everything now works as expected and EncryptedPropertySourcesPlaceholderConfigurer.convertProperties() receives all of the properties values from both files.

Written one sample application to do add support for using Encrypted values here
https://github.com/pushpendra-jain/spring-examples/tree/master/PropertySourcesPlaceholderEncrypter
Basically instead of trying to invoke its convertProperty method overrided its processProperties method and getting the job done and code surely does not change any existing behavior.
see https://github.com/pushpendra-jain/spring-examples/blob/master/README.md for steps.

Related

Spring - Load custom property file based on value from application.properties

I have application.properties file:
value=a
Then I would like to load a property file based on that value - a.properties and read/use properties from that file.
I was thinking about something like this:
#Configuration
public class PropertiesConfiguration {
#Value("${value}")
private String value;
#Bean
public PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
configurer.setLocation(new ClassPathResource(value + ".properties"));
return configurer;
} }
but the value is always null for some reason. When I try to get that value e.g. in service/component then it works fine. I would like to avoid using spring profiles. Any ideas how to achieve that with latest Spring?
In the line configurer.setLocation(new ClassPathResource(value + ".properties")); it should be "application.properties" since your file name is application.properties
Also in the properties file define it as
value = a
with spaces
One of the solutions I have come across and works fine is use of Component and PropertySource annotations.
#Component
#PropertySource(value = "classpath:${value}.properties")
public class CountryService implements ICountryService {
#Value("${<whatever in a.properties file>}")
private String currency;
#Override
public String getCurrency() {
return currency;
}
}
where ${value} is taken from application.properties. Then Autowire that bean whenever needed.

Spring Properties Decryption

We have mix of some legacy spring apps which are not yet migrated to spring-boot or spring cloud and also spring boot apps. I am working on creating a Spring component that will automatically decrypt spring properties when the environment is loaded if the property value is encrypted and has a prefix. The properties can be in .properties files(for legacy apps) or in .yaml files(newer spring boot apps).
The component should be able to decrypt any spring property regardless of the source, and should work with any spring version and not tied to spring boot.The component should also transparently decrypt properties. It should read passphrase from a property file, so the passphrase file needs to be loaded in the beginning.
We have our own ecrypt/decrypt and don't want to use jaspyt.
Things tried so far:
I liked this approach of creating an ApplicationListener, but this is tied up with spring boot(ApplicationEnvironmentPreparedEvent). With Spring events like ContextRefreshed or ContextStart , i don't see how can i get ConfigurableApplicationContext/ConfigurableEnvironment. Anyone created a Listener for encrypt/decrypt withouth spring boot/cloud?
I also created a custom ApplicationContextInitializer, and added it to web.xml's context-param, but this doesn't seems to be working. When i debug into it, i don't think it is loading/reading properties from my app.properties file.
#Component
public class DecryptingPropertyContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize( ConfigurableApplicationContext applicationContext ) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
for ( PropertySource<?> propertySource : environment.getPropertySources() ) {
Map<String, Object> propertyOverrides = new LinkedHashMap<>();
decodePasswords( propertySource, propertyOverrides );
if ( !propertyOverrides.isEmpty() ) {
PropertySource<?> decodedProperties = new MapPropertySource( "decoded " + propertySource.getName(),
propertyOverrides );
environment.getPropertySources().addBefore( propertySource.getName(), decodedProperties );
}
}
}
private void decodePasswords(PropertySource<?> source, Map<String, Object> propertyOverrides) {
if ( source instanceof EnumerablePropertySource ) {
EnumerablePropertySource<?> enumerablePropertySource = (EnumerablePropertySource<?>) source;
for ( String key : enumerablePropertySource.getPropertyNames() ) {
Object rawValue = source.getProperty( key );
if ( rawValue instanceof String ) {
//decrypt logic here
propertyOverrides.put( key, decryptedValue );
}
}
}
}
}
Does anyone had to do something similar or has any better ideas ? Is there a way i can listen to application events and then process?
Appreciate your help
You can write your own PropertiesFactoryBean and override createProperties to decrypt encrypted values:
public class DecryptingPropertiesFactoryBean extends PropertiesFactoryBean {
#Override
protected Properties createProperties() throws IOException {
final Properties encryptedProperties = super.createProperties();
final Properties decryptedProperties = decrypt(encryptedProperties);
return decryptedProperties;
}
}
and a PropertySourcesPlaceholderConfigurer bean using these properties:
#Configuration
public class PropertiesConfiguration {
#Bean
public static DecryptingPropertiesFactoryBean propertyFactory() {
final DecryptingPropertiesFactoryBean factory = new DecryptingPropertiesFactoryBean();
final Resource[] propertyLocations = new Resource[] {
new FileSystemResource(new File("path/to/file.properties"))
};
factory.setLocations(propertyLocations);
return factory;
}
#Bean
public static Properties properties() throws Exception {
return propertyFactory().getObject();
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
final PropertySourcesPlaceholderConfigurer bean = new PropertySourcesPlaceholderConfigurer();
bean.setIgnoreResourceNotFound(true);
bean.setIgnoreUnresolvablePlaceholders(false);
bean.setProperties(properties());
return bean;
}
}

Spring Boot: Make property non-configurable

In Spring Boot I have several options to externalize my configuration. However, how can I make such properties non-configurable, i.e. readonly.
Concretly, I want to set server.tomcat.max-threads to a fixed value and do not want somebody who is going to start the application to have the ability to change it. This could easily be done by passing it as a command line argument for instance.
It's probably not possible by default, maybe someone could suggest workarounds?
You have 2 options
Set System.setProperty("prop", "value") Property hard coded
Use properties that will override all other properties
Set system property hard coded
public static void main(String[] args) {
System.setProperty("server.tomcat.max-threads","200");
SpringApplication.run(DemoApplication.class, args);
}
Properties in secure.properties will override all others (see, Prevent overriding some property in application.properties - Spring Boot)
#Configuration
public class SecurePropertiesConfig {
#Autowired
private ConfigurableEnvironment env;
#Autowired
public void setConfigurableEnvironment(ConfigurableEnvironment env) {
try {
final Resource resource = new
ClassPathResource("secure.properties");
env.getPropertySources().addFirst(new
PropertiesPropertySource(resource.getFilename(),
PropertiesLoaderUtils.loadProperties(resource)));
} catch (Exception ex) {
throw new RuntimeException(ex.getMessage(), ex);
}
}
I ended up implementing an ApplicationContextInitializer (Docu), which in its initialize() method simply sets the static value programatically:
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
Map<String, Object> props = new HashMap<>();
props.put(MAX_THREADS, MAX_THREADS_VAL);
environment.getPropertySources().addFirst(new MapPropertySource("tomcatConfigProperties", props));
}
Another possible solution was found here: Prevent overriding some property in application.properties - Spring Boot

Spring - How to get subset of properties file without enumerating all required properties

I am loading my properties file as following:
#Configuration
#PropertySource("classpath:app.properties")
class MyApp {
#Bean
public PropertySourcesPlaceholderConfigurer PropertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
In the properties file, I have several database-related properties:
database.dataSource.url=jdbc:postgresql://localhost:${db-port:5432}/mydb
database.dataSource.x=...
database.dataSource.y=...
database.dataSource.z=...
Note:
${db-port} should be replaced by either the value of property/environment variable db-port or 5432. In my case, I am defining the environment variable db-port when spawning the Tomcat container.
All database-related properties are grouped under database. root. This is intentional, see below.
I want to avoid that I have to enumerate/hardcode all possible database-related properties in my code. Luckily, the database layer in use (Hikari) has the nice feature that I can pass all properties via a java.util.Properties. So, I want retrieve all defined properties under database.* and simply forward it to Hikari.
For this, I wrote the following utility:
#Component
public class PropertyFetcher
{
#Autowired
private ConfigurableEnvironment environment;
public Properties get(final String key) {
final Properties p = new Properties();
for (final PropertySource<?> s : environment.getPropertySources()) {
if (s instanceof EnumerablePropertySource) {
for (final String k : ((EnumerablePropertySource) s).getPropertyNames()) {
if (k.startsWith(key) && k.length() > key.length()) {
p.put(k.substring(key.length()), s.getProperty(k));
}
}
}
}
return p;
}
}
Now, when calling get("database."), I have all database-related properties as defined in the properties file. Great! But, the value for property dataSource.url is now
jdbc:postgresql://localhost:${db-port:5432}/mydb
instead of
jdbc:postgresql://localhost:9876/mydb
So, for some reason, the ${db-port:5432} is not resolved (yet?) when going via this route (ConfigurableEnvironment).
How can this be fixed? Or is there a better way to get all properties under a certain root without having to enumerate/hardcode them into the code?
Please note that in the default scenario, the ${db-port:5432} in property database.dataSource.url=jdbc:postgresql://localhost:${db-port:5432}/mydb is correctly resolved. I tested this by defining the following member and logging it:
#Value("${database.dataSource.url}")
final String url; // holds jdbc:postgresql://localhost:9876/mydb
You should read the property values from real environment only. Then only you will get actual or effective value of a property.
This will require a little change in your code.
change this line:
p.put(k.substring(key.length()), s.getProperty(k));
to this:
p.put(k.substring(key.length()), environment.getProperty(k));

Springboot not replacing environment variables in application.properties file

I am trying to run Quartz Scheduler using SpringBoot. Using Quartz Jdbc Data Store. Due to security reasons , we wish to pick the Db credentials from the properties file. From what I understand from here(Using env variable in Spring Boot's application.properties) and springBoot docs, SpringBoot automatically replaces environment variables in application.properties but I am not seeing this . Here is my system environment file which i am sourcing before running the application
export DB_HOST=localhost
export DB_PORT=11111
export DB_USER=root
export DB_PASSWORD=root
export QUARTZ_DB_NAME=quartz
And here is my application.properties
org.quartz.dataSource.quartzDataSource.URL =jdbc:mysql://${DB_HOST}:${DB_PORT}/${QUARTZ_DB_NAME}
org.quartz.dataSource.quartzDataSource.user = ${DB_USER}
org.quartz.dataSource.quartzDataSource.password = ${DB_PASSWORD}
And my configuration class
#Configuration
public class ConfigureQuartz {
#Autowired
private ApplicationContext applicationContext;
#Bean
public SchedulerFactoryBean schedulerFactoryBean() throws IOException
{
final SchedulerFactoryBean quartzScheduler = new SchedulerFactoryBean();
quartzScheduler.setSchedulerName("mdsScheduler");
quartzScheduler.setQuartzProperties(quartzProperties());
final AutoWiringSpringBeanJobFactory jobFactory = new AutoWiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
quartzScheduler.setJobFactory(jobFactory);
return quartzScheduler;
}
#Bean
public Properties quartzProperties() throws IOException {
final PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/application.properties"));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
But when i run my spring application using java -jar <>.java , I dont see the values substitued .
I can workaround by reading the values using System.getEnv() but would be great if the values can be substitued . Not sure why its not working :(
Spring-boot allows us several methods to provide externalized configurations , you can try using application.yml or yaml files instead of the property file and provide different property files setup according to different environments.We can separate out the properties for each environment into separate yml files under separate spring profiles.Then during deployment you can use :
java -jar -Drun.profiles=SpringProfileName
to specify which spring profile to use.Note that the yml files should be name like
application-{environmentName}.yml
for them to be automatically taken up by springboot.
Reference : https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-profile-specific-properties
To read from the application.yml or property file :
The easiest way to read a value from the property file or yml is to use the spring #value annotation.Spring automatically loads all values from the yml to the spring environment , so we can directly use those values from the environment like :
#Component
public class MyBean {
#Value("${name}")
private String name;
// ...
}
Or another method that spring provides to read strongly typed beans is as follows:
YML
acme:
remote-address: 192.168.1.1
security:
username: admin
roles:
- USER
- ADMIN
Corresponding POJO to read the yml :
#ConfigurationProperties("acme")
public class AcmeProperties {
private boolean enabled;
private InetAddress remoteAddress;
private final Security security = new Security();
public boolean isEnabled() { ... }
public void setEnabled(boolean enabled) { ... }
public InetAddress getRemoteAddress() { ... }
public void setRemoteAddress(InetAddress remoteAddress) { ... }
public Security getSecurity() { ... }
public static class Security {
private String username;
private String password;
private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
public String getUsername() { ... }
public void setUsername(String username) { ... }
public String getPassword() { ... }
public void setPassword(String password) { ... }
public List<String> getRoles() { ... }
public void setRoles(List<String> roles) { ... }
}
}
The above method works well with yml files.
Reference:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html
To read from the environment :
If you are running the application on linux set the env as below :
export DB_HOST=localhost
export DB_PORT=11111
export DB_USER=root
export DB_PASSWORD=root
export QUARTZ_DB_NAME=quartz
if you are on windows set it like :
SET DB_HOST=localhost
SET DB_PORT=11111
SET DB_USER=root
SET DB_PASSWORD=root
SET QUARTZ_DB_NAME=quartz
In your application.properties if you keep the keys as below , it should automatically resolve the value from the environment :
spring.datasource.url = ${DB_HOST}/"nameofDB"
spring.datasource.username = ${DB_USER}
spring.datasource.password = ${DB_PASSWORD}

Categories