Jasypt Spring EncryptablePropertyPlaceholderConfigurer not reading VM arguments - spring

Ever since I replaced Spring's PropertySourcesPlaceholderConfigurer with Jasypt's EncryptablePropertyPlaceholderConfigurer, I've not been able to read the VM arguments passed to the application (eg: -DtestVar=testVal) through #Value annotations. The properties from app.properties file is being read and/or decrypted as expected. But the VM arguments are just not read. It is supposed to override the properties on app.properties with the value passed as VM argument.
What the config used to be (when VM arguments were being read and working as expected):
#Bean
public static PropertySourcesPlaceholderConfigurer myConfig() {
return new PropertySourcesPlaceholderConfigurer();
}
My current Jasypt EncryptablePropertyPlaceholderConfigurer config looks like this, which replaced the above:
#Bean
public static PooledPBEStringEncryptor configuEncryptor() {
PooledPBEStringEncryptor configuEncryptor = new PooledPBEStringEncryptor();
configuEncryptor.setConfig(environmentVariableConfig());
return configuEncryptor;
}
#Bean
public static EnvironmentStringPBEConfig environmentVariableConfig() {
final EnvironmentStringPBEConfig config = new EnvironmentStringPBEConfig();
config.setAlgorithm(ALGORITHM);
config.setPassword(PASSWORD);
return config;
}
#Bean
public static EncryptablePropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
EncryptablePropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new EncryptablePropertyPlaceholderConfigurer(configuEncryptor());
propertyPlaceholderConfigurer.setLocations(
new ClassPathResource("app.properties")
);
propertyPlaceholderConfigurer.setIgnoreResourceNotFound(true);
propertyPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(true);
return propertyPlaceholderConfigurer;
}
There were no issues reading externally passed VM arguments(which overrides props in app.properties) when Spring's PropertySourcesPlaceholderConfigurer was being used. This has become an issue after implementing Jasypt's EncryptablePropertyPlaceholderConfigurer. Have anyone been able to solve this?

Related

Spring Boot: Multiple WAR deployment in same tomcat different properties

So I have to deploy the same springboot app as multiple apps in the same tomcat server.
eg /app1 /app2 /app3.
They share most of the same configuration except for datasource configuration.
I've been searching for a way to externalise the datasource configuration based on the servlet-context or something like that.
Using springs externalised configuration, I am able to get it to load the same external data source file for all apps, but they need to be different. eg.
#PropertySource(value = "file:${HOME}/datasource-override.properties", ignoreResourceNotFound=false)
Using the embedded tomcat mode, ie via say .\gradlew bootRun I think I can achieve it.
I just need to use the following as the application.properties for that profile sets the server.context-path property. (as this is a single app) eg.
#PropertySource(value = "file:${HOME}/${server.context-path}/datasource-override.properties", ignoreResourceNotFound=false),
Searching around, I thought it might be something like (and combinations of) the following, but it didn't work. eg
#PropertySource(value = "file:${HOME}/${server.servlet.context-path}/datasource-override.properties", ignoreResourceNotFound=false)
All examples I've found so far deal with either the embedded tomcat, or a single externalised property file for a single app.
Ideally I would like it to find the file in either it's own directory
file:${HOME}/${server.servlet.context-path}/datasource.properties
So for the three apps it would be something like the following, where it detects from it's deployed context, what the location for it's property file is. eg:
file:${HOME}/app1/datasource.properties
file:${HOME}/app2/datasource.properties
file:${HOME}/app3/datasource.properties
Obviously if the app was deployed as /funky_chicken then it would have a matching funky_chicken/datasource.properties
Any thoughts ? I know I am probably close, and I've tried dumping all the environmental properties. (you are probably are going to tell me to get it from JNDI as it's the only one I haven't dumped looking for the context)
Now I know ${HOME} is not the best place to store config items, it really is just to make it easier to describe the issue.
Update:
Thanks to the suggestions to use profiles, is there a way to have three active profiles in the one tomcat server, /app1, /app2 and /app3 from the same WAR file?
Why you want to deploy in tomcat? Springboot app can work lonely. Hope below steps helpful to you.
add application.yml(application.properties is ok too) in /resources. In this file, you configure common setting here.
Then you add files named from application-app1.yml to application-app3.yml in /resources too. In these files, you configure different db setting.
launch your app: for example, I suppose app1 using port 10000, app2 using port 10001...
after maven well,
app1: java -jar target/[***SNAPSHOT].jar --server.port=10000 --spring.profiles.active=app1
app2: java -jar target/[***SNAPSHOT].jar --server.port=10001 --spring.profiles.active=app2
app3: java -jar target/[***SNAPSHOT].jar --server.port=10002 --spring.profiles.active=app3
You can solve problem with spring profiles and there is no need to use #PropertySource
for application 1 just activate profiles: spring.profiles.active=app1 - this assume that in classpath you have application-app1.properties file. Same for app2, app3..appN. And file application.properies will contains common properties for all of services
I had a similar requirement. My approach is to pass the config directory to tomcat as environment variable, lets assume you pass
spring.config.location=/home/user/config
Then under /home/user/config folder you should have files matching the contextPath for each application instance. In your case you should have
app1.properties
app2.properties
app3.properties
If you don't want to duplicate common parameters, you can put all common properties in a separate file and use "spring.config.import" to import it from each application specific configuration file. Note that importing another file is supported since Spring Boot 2.4 See section "Importing Additional Configuration"
For Spring Boot application to load the properties file according to the context path, you should override "createRootApplicationContext" to get the context path, and override "configure" to set it as the properties file name as below.
#SpringBootApplication
public class TestApp extends SpringBootServletInitializer {
private static Class<TestApp> applicationClass = TestApp.class;
private String contextPath;
public static void main(String[] args) {
try {
SpringApplication.run(applicationClass, args);
}
catch (Exception e) {
// Handle error
}
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(applicationClass).properties("spring.config.name:" + contextPath);
}
#Override
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
contextPath = servletContext.getContextPath();
return super.createRootApplicationContext(servletContext);
}
}
You can try the RoutingDataSource approach, This lets you switch the datasource at runtime/realtime.
To implement this you have to pass some datasource identifier initially (You can set it in your auth token for rest based requests or in a session)
eg -
localhost:80/yourcontext/{app1}
localhost:80/yourcontext/{app2}
localhost:80/yourcontext/app3
Here app1, app2, app3 will be your datasource identifiers
App controller
#Controller
public class AppController extends SuperController{
#GetMapping("/{client}")
public String login(ModelMap map, #PathVariable("client") String client, HttpServletRequest request){
//Logic to set the path variable
return "loginPage";
}
}
Routing datasource Config
#Configuration
#EnableTransactionManagement
#ComponentScan({ "com.components_package" })
#PropertySource(value = { "classpath:database.properties" })
public class RoutingDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
String tenant = DbContextHolder.getDbType();
LoginDetails LoginDetails = currentUser();
if(LoginDetails != null){
tenant = LoginDetails.getTenantId();
}
logger.debug("tenant >>>> "+tenant);
return tenant;
}
private LoginDetails currentUser() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication instanceof UserAuthentication) {
return ((UserAuthentication) authentication).getDetails();
}
//If not authenticated return anonymous user
return null;
}
}
Hibernate config
#PropertySource(value = { "classpath:database.properties" })
public class HibernateConfiguration {
#Autowired
private Environment environment;
#Bean
#Primary
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dynamicDataSource());
em.setPackagesToScan("com.entities_package");
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(hibernateProperties());
return em;
}
/** RoutingDataSource datasource initialized enabling the application
* to switch multiple databases at runtime
* #return
*/
private RoutingDataSource dynamicDataSource(){
RoutingDataSource routingDataSource = new RoutingDataSource();
/*Set default datasource*/
routingDataSource.setDefaultTargetDataSource(defaultDataSource());
/*Set all datasource map*/
routingDataSource.setTargetDataSources(fetchDatasourceMap());
routingDataSource.afterPropertiesSet();
return routingDataSource;
}
/** This is a default datasource
* #return
*/
private DataSource defaultDataSource() {
final JndiDataSourceLookup dsLookup = new JndiDataSourceLookup();
DataSource dataSource = dsLookup.getDataSource("jdbc/defaultDs");
return dataSource;
}
/** This method sets all predefined client specific datasources in a map
* #return Map<Object, Object>
*/
private Map<Object, Object> fetchDatasourceMap(){
Map<Object, Object> dataSourcesMap = new HashMap<>();
//database.clients=app1,app2,app3
String client = environment.getRequiredProperty("database.clients");
String[] allClients = client.split(",");
if(allClients != null && allClients.length > 0){
for (Integer i = 0; i < allClients.length; i++) {
String clientKey = allClients[i].trim();
dataSourcesMap.put(clientKey, dataSource(clientKey));
}
}
return dataSourcesMap;
}
private DataSource dataSource(String clientKey) {
final JndiDataSourceLookup dsLookup = new JndiDataSourceLookup();
String lookupKey = "jdbc/"+clientKey;
DataSource dataSource = dsLookup.getDataSource(lookupKey);
return dataSource;
}
private Properties hibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
// properties.put("hibernate.format_sql",
// environment.getRequiredProperty("hibernate.format_sql"));
return properties;
}
#Bean
JpaTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
}
You have to define your datasource properties in context.xml for JNDI lookup.
Hope this helps

Different yml files for different Beans in Spring

In my spring boot application, I have the main application.yml file. I have a lot of properties, and therefore I would like to have another yml files, which contains the specified properties, grouped by their logic or something.
How can I configure a Bean, to load and work all the properties from one new yml file, and another Bean from another new yml? What is the best practice for it?
I found examples using YamlPropertiesFactoryBean, and this bean can read several resources (yml files), but in an another Bean, when I autowire this YamlPropertiesFactoryBean, I cannot get that specific yml, because the getObject() of this YamlPropertiesFactoryBean will have all the yml resources I added to it.
Finally I have it! This is how it works:
I have a properties configuration class, which loads the yml files:
#Configuration
public class PropertiesConfig {
public static final String PERSONS_FILE_NAME = "persons.yml";
public static final String FOODS_FILE_NAME = "foods.yml";
#Bean
public PropertySourcesPlaceholderConfigurer properties() {
final PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
final YamlPropertiesFactoryBean personsYaml = personsPropertiesFromYamlFile();
final YamlPropertiesFactoryBean foodsYaml = foodsPropertiesFromYamlFile();
propertySourcesPlaceholderConfigurer.setPropertiesArray(personsYaml.getObject(), foodsYaml.getObject());
return propertySourcesPlaceholderConfigurer;
}
#Bean
#Qualifier(PersonsManager.QUALIFIER_NAME)
public YamlPropertiesFactoryBean personsPropertiesFromYamlFile() {
final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ClassPathResource(PERSONS_FILE_NAME));
return yaml;
}
#Bean
#Qualifier(FoodsManager.QUALIFIER_NAME)
public YamlPropertiesFactoryBean foodsPropertiesFromYamlFile() {
final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ClassPathResource(FOODS_FILE_NAME));
return yaml;
}
}
And finally, I have two beans (managers), which hold only the corresponding yml properties:
#Component
public class PersonsManager extends YmlPropertiesManager {
public static final String QUALIFIER_NAME = "personsYaml";
#Autowired
public PersonsManager(#Qualifier(QUALIFIER_NAME) YamlPropertiesFactoryBean yamlObject) {
super(yamlObject);
}
...
}
and:
#Component
public class FoodsManager extends YmlPropertiesManager {
public static final String QUALIFIER_NAME = "personsYaml";
#Autowired
public FoodsManager(#Qualifier(QUALIFIER_NAME) YamlPropertiesFactoryBean yamlObject) {
super(yamlObject);
}
...
}
So the main thing here is the #Qualifier annotation.
Beans shouldn't be aware of yaml files. The yaml files are just sources that use used to build up the Spring Environment instance.
If you want specific properties for specific beans, the best way is to prefix those properties in application.yaml, and then use the #ConfigurationProperties with an argument of the prefix you want to use, to bind those properties to the bean in question.
See here:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html

Factory method 'amazonS3Client' : Access key cannot be null

Trying to connect with aws-s3 using spring boot application. when i am using
import org.springframework.cloud.aws.context.support.io.ResourceLoaderBeanPostProcessor
getting aws key can not be null.
#Configuration
#EnableContextResourceLoader
#EnableContextCredentials
public class S3Configuration {
#Value("${cloud.aws.credentials.accessKey}")
private String ACCESS_KEY;
#Value("${cloud.aws.credentials.secretKey}")
private String SECRET_KEY;
#Value("${cloud.aws.region}")
private String region;
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public AmazonS3Client amazonS3Client() {
return new AmazonS3Client(new BasicAWSCredentials(ACCESS_KEY, SECRET_KEY));
}
#Bean
public ResourceLoaderBeanPostProcessor resourceLoaderBeanPostProcessor() {
return new ResourceLoaderBeanPostProcessor(amazonS3Client());
}
}
If I dont use "ResourceLoaderBeanPostProcessor" class then AmazonS3Client object is creating successfully by reading properties form application.properties.
can some one help me what i am doing wrong?
I'm pretty sure that the BeanFactoryPostProcessor class that the ResourceLoaderBeanPostProcessor implements, is executed before values from application.properties are loaded/injected by the spring application. Therefore your values (when creating the bean) is null. (And that's why it works when you're not using that paticular class).
I would suggest using the default credentials chain, and have your credentials lying in a ~/.aws/ folder (on your local machine, and on your servers). see http://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html for more detail.

How can I build a database-based Spring Boot environment / property source?

The goal is running Spring Boot application with an Environment containing keys & values loaded and generated by a database connection (DataSource).
Or, more abstract defined: While a configuration by files only should be preferred (faster, easier, more tolerant, ...), sometimes you will find use cases where a non-static files based configuration is required.
Spring 3.1 introduces Environment which is actually a property resolver (extends PropertyResolver) and is based on a list of objects PropertySource. Such a source is a wrapper/adapter for a properties (file or object), a map or something else. It really looks like this is the way how to get.
Properties properties = new Properties();
properties.put("mykey", "in-config");
PropertiesPropertySource propertySource = new PropertiesPropertySource("myProperties", properties);
However, this cannot be done in #Configuration classes since it must be available for the configuration phase. Think about something like
#Bean public MyService myService() {
if ("one".equals(env.getProperty("key")) {
return new OneService();
} else {
return new AnotherService();
}
}
// alternatively via
#Value("${key}")
private String serviceKey;
Additionally, the more recent Spring releases support Condition as well.
With a OneCondition like
public class OneCondition implements Condition {
#Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
return "one".equals(context.getEnvironment().getProperty("key"));
}
}
This can be used like
#Bean
#Conditional(OneCondition.class)
public MyService myService() {
return new OneService();
}
My non working ideas:
Option 1: #PropertySource
The corresponding annotation processor handles files only. This is fine, but not for this use case.
Option 2: PropertySourcesPlaceholderConfigurer
An example with a custom property source would be
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() throws IOException {
PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
pspc.setIgnoreUnresolvablePlaceholders(Boolean.TRUE);
// create a custom property source and apply into pspc
MutablePropertySources propertySources = new MutablePropertySources();
Properties properties = new Properties();
properties.put("key", "myvalue");
final PropertiesPropertySource propertySource = new PropertiesPropertySource("pspc", properties);
propertySources.addFirst(propertySource);
pspc.setPropertySources(propertySources);
pspc.setLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:application.properties"));
return pspc;
}
However, this only configures the placeholders (i.e. #Value. Any environment.getProperty() will not profit.
This is more or less the same as Option 1 (less magic, more options).
Do you know a better option? Ideally, the solution would use the context datasource. However, this is conceptually an issue since the datasource bean creation relies on properties itself...
Spring Boot provides some different extensions point for this early processing step: http://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-boot-application.html#howto-customize-the-environment-or-application-context
Internally, these options are realised with implementations of standard Spring ApplicationContextInitializer.
Depending on the priority of the source, the key/value will be available both in environment.getProperty() as well in property placeholders.
Because these is a pre-config-context listeners, no other beans are available, like a DataSource. So if the properties should be read from a database, the datasource and connection have to be build manually (eventually a separated datasource connection lookup).
Option: ApplicationListener for ApplicationEnvironmentPreparedEvent
Build an implementation of an application listener consuming ApplicationEnvironmentPreparedEvents and
register it in META-INF/spring.factories and the key org.springframework.context.ApplicationListener
- or -
use the SpringApplicationBuilder:
new SpringApplicationBuilder(App.class)
.listeners(new MyListener())
.run(args);
Example
public class MyListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
#Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
final ConfigurableEnvironment env = event.getEnvironment();
final Properties props = loadPropertiesFromDatabaseOrSo();
final PropertiesPropertySource source = new PropertiesPropertySource("myProps", props);
environment.getPropertySources().addFirst(source);
}
}
Reference: http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-spring-application.html#boot-features-application-events-and-listeners
Option: SpringApplicationRunListener
Besides the special event, there is also a more general event listener containing hooks for several types of events.
Build an implementation of SpringApplicationRunListener and register it in META-INF/spring.factories and the key org.springframework.boot.SpringApplicationRunListener.
Example
public class MyAppRunListener implements SpringApplicationRunListener {
// this constructor is required!
public MyAppRunListener(SpringApplication application, String... args) {}
#Override
public void environmentPrepared(final ConfigurableEnvironment environment) {
MutablePropertySources propertySources = environment.getPropertySources();
Properties props = loadPropertiesFromDatabaseOrSo();
PropertiesPropertySource propertySource = new PropertiesPropertySource("myProps", props);
propertySources.addFirst(propertySource);
}
// and some empty method stubs of the interface…
}
Reference: http://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-boot-application.html#howto-customize-the-environment-or-application-context
Option: ApplicationContextInitializer
This is an old friend for all "non Boot" Spring developers. However, SpringApplication mocks a configuration away -- at first.
Build an implementation of ApplicationContextInitializer and
register it in META-INF/spring.factories and the key org.springframework.context.ApplicationContextInitializer.
- or -
use the SpringApplicationBuilder:
new SpringApplicationBuilder(App.class)
.initializers(new MyContextInitializer())
.run(args);
Example
public class MyContextInitializer implements ApplicationContextInitializer {
#Override
public void initialize(final ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
MutablePropertySources propertySources = environment.getPropertySources();
Properties props = loadPropertiesFromDatabaseOrSo();
PropertiesPropertySource propertySource = new PropertiesPropertySource("myProps", props);
propertySources.addFirst(propertySource);
}
}

Why does the ConfigurationPropertiesBindingPostProcessor considers only one PropertySourcesPlaceholderConfigurer?

The ConfigurationPropertiesBindingPostProcessor is used whenever a bean is annotated with #ConfigurationProperties in order to bind property values to a bean.
The implementation of ConfigurationPropertiesBindingPostProcessor (Spring Boot version 1.3.1) determines its property sources in the method deducePropertySources() as shown below:
private PropertySources deducePropertySources() {
PropertySourcesPlaceholderConfigurer configurer = getSinglePropertySourcesPlaceholderConfigurer();
if (configurer != null) {
// Flatten the sources into a single list so they can be iterated
return new FlatPropertySources(configurer.getAppliedPropertySources());
}
...
}
When looking into getSinglePropertySourcesPlaceholderConfigurer() you'll discover that only the first PropertySourcesPlaceholderConfigurer is taken and the others are ignored.
What is the reason for this?
In my Spring configuration I have multiple PropertySourcesPlaceholderConfigurer beans and I would expect that all of them are used when binding the property values.
Of course I can create a workaround by defining an interface MyPropertySourceResources that looks like this:
public interface MyPropertySourceResources {
List<Resource> getResources();
}
and in my Spring configuration I define only one PropertySourcesPlaceholderConfigurer that takes a list of MyResourceLocartion as parameter:
#Bean
public static PropertySourcesPlaceholderConfigurer gdaPropertySourcesPlaceholderConfigurer(
List<MyPropertySourceResources> propertySourceResources) {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
List<Resource> allResources = propertySourceResources.stream()
.flatMap(p -> p.getResources().stream())
.collect(Collectors.toList());
propertySourcesPlaceholderConfigurer.setLocations(allResources.toArray(new Resource[allResources.size()]));
return propertySourcesPlaceholderConfigurer;
}
#Bean
public MyPropertySourceResources gdaPropertySourceResources() {
return () -> Arrays.asList(new FileSystemResource(new File("path/to/my.properties")));
}
In this solution each module defines its own MyPropertySourceResources bean and all of them will be used to build up the only PropertySourcesPlaceholderConfigurer.
But back to my question: Is there a good reason why the ConfigurationPropertiesBindingPostProcessor takes only one PropertySourcesPlaceholderConfigurer into account?

Resources