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;
}
}
Related
#Singleton
public class TestFunction extends FunctionInitializer {
Logger log = LoggerFactory.getLogger(TestFunction.class);
public TestFunction() {
}
public String execute() {
return "Hello";
}
}
I want to override datasource properties in application.yml file programmatically, but without using bean created event listener. Is there a way to do that. Like creating a custom application context with properties.
I have used the below approach for Micronaut API gateway proxy.
public class StreamLambdaHandler implements RequestStreamHandler {
.......
public StreamLambdaHandler() {
try {
log.info("Initializing Lambda Container");
this.dbCredentialService = new DBCredentialService();
// Get updated database credential map
Map<String, Object> props = this.dbCredentialService.getDbCredential();
// Create application context builder with updated properties
// i.e Override datasources properties in application.yml
builder = ApplicationContext.build().properties(props);
handler = new MicronautLambdaContainerHandler(builder);
}....
........
}
Can we do something similar with FunctionInitializer?
If you plan to override only datasource credentials properties it could be done this way.
#Factory
public class HikariDataSourceFactory {
#Bean
#Primary
public DataSource dataSource(DBCredentialService credentialService) throws URISyntaxException {
Map<String, Object> credentials = this.dbCredentialService.getDbCredential();
String username = "user";
String password = credentials.get("username");
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/postgres");
config.setUsername(username);
config.setPassword(password);
config.setDriverClassName("org.postgresql.Driver");
return new HikariUrlDataSource(config);
}
}
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
How do I access the #Value machinery dynamically at run-time?
I thought that Environment might be what I was looking for, but it
#Component
public class SpringConfiguration implements ConfigurationI {
#Autowired
private Provider<Environment> env;
#Override
public String get(String key) {
try {
return env.get().getRequiredProperty(key);
} catch (IllegalStateException e) {
return null;
}
}
}
Unfortunately, this does not access the values exposed by our PropertyPlaceholderConfigurer bean.
EDIT: To explain my use case: This is part of making a library with a lot of spring specific pieces (that a pile of older spring applications depend on) usable from newer Guice applications by switching Spring specific annotations for JSR 330 (javax.inject) ones. I was hoping to avoid rewriting all the PropertyPlaceholderConfigurer stuff across all our Spring applications, by providing a nice entrypoint like this. If there is another better way to do this (maybe with #Named?) then I am all ears.
EDIT2: This is a (cleaned up) example of what kind of PropertyPlaceholderConfigurer exists in the apps calling into this library.
#Bean
public PropertyPlaceholderConfigurer placeholderConfigurer() {
return new PropertyPlaceholderConfigurer() {
#Override
protected String resolvePlaceholder(String placeholder, Properties props) {
// Some code to parse and cleanup key here
String result = getPropertyFromLocalAppSpecificConfig(key);
if (result == null) {
result = super.resolvePlaceholder(placeholder, props);
}
// Some more random app specific logic for missing defaults
return result;
}
};
}
PropertyPlaceholder and friends do not put the properties in your Environment (mainly because of backward compatibility reasons). Instead they use Environment and its own internal Properties object gathered generally from property files from the classpath to resolve #Value properties. Thus the properties loaded from PropertyPlaceholder can not be fetched dynamically (ie no getProperty(String..)).
Some people create custom PropertyPlaceholder that store the properties publicly (through getter or whatever) but I think completely defeats Spring's new unified environment configuration handling.
What you really want is probably #PropertySource which still is pretty crappy since its not dynamic (since its an annotation you can't change where files get loaded from) but it will load properties into the Environment. I have been meaning to file issues with Spring Source about the confusion of this.
Anyway you can look at my solution here: Manually add a #PropertySource: Configuring Environment before context is refreshed
Basically you need to get hold of ConfigurableEnvironment and load your properties into it by creating PropertySources. The API for this is very powerful but not very intuitive. You can use ApplicationContextInitializers to get the Environment which has its own annoying issues (see link) or you can do what I do below.
public class ConfigResourcesEnvironment implements
ResourceLoaderAware, EnvironmentAware, BeanDefinitionRegistryPostProcessor, EnvironmentPropertiesMapSupplier {
private Environment environment;
private Map<String, String> environmentPropertiesMap;
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (environment instanceof ConfigurableEnvironment) {
ConfigurableEnvironment env = ((ConfigurableEnvironment) this.environment);
List<PropertySource> propertySources;
try {
propertySources = loadPropertySources(); //Your custom method for propertysources
} catch (IOException e) {
throw new RuntimeException(e);
}
//Spring prefers primacy ordering so we reverse the order of the sources... You may not need to do this.
reverse(propertySources);
for (PropertySource rp : propertySources) {
env.getPropertySources().addLast(rp);
}
environmentPropertiesMap = ImmutableMap.copyOf(environmentPropertiesToMap(env));
}
else {
environmentPropertiesMap = ImmutableMap.of();
}
}
public static Map<String,String> environmentPropertiesToMap(ConfigurableEnvironment e) {
Map<String, String> properties = newLinkedHashMap();
for (String n : propertyNames(e.getPropertySources())) {
String v = e.getProperty(n);
if (v != null)
properties.put(n, v);
}
return properties;
}
public static Iterable<String> propertyNames(PropertySources propertySources) {
LinkedHashSet<String> propertyNames = new LinkedHashSet<String>();
for (PropertySource<?> p : propertySources) {
if (p instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> e = (EnumerablePropertySource<?>) p;
propertyNames.addAll(asList(e.getPropertyNames()));
}
}
return propertyNames;
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
//NOOP
}
#Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
public Map<String, String> getEnvironmentPropertiesMap() {
return environmentPropertiesMap;
}
}
Once you have ConfigurableEnvironment loaded you can use the EnvironmentAware interface for things that need the Environment or create your own interface.
Here is a custom interface you can use for things that need dynamic properties (the above class implements it):
public interface EnvironmentPropertiesMapSupplier {
public Map<String, String> getEnvironmentPropertiesMap();
}
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.
We are developing a Spring MVC (v4) web app using the Thymeleaf templating library in the view layer with the Thymeleaf SpringTemplateEngine providing SPEL support.
When we reference types in our templates (e.g. for access to static utility methods or enums) we have to include the fully qualified name as the Spring StandardEvaluationContext StandardTypeLocator only knows about the java.lang package by default. I can see in the Spring API that we need to add our own packages to the type locator using the registerImport(String prefix) method, but I can't work out how to get hold of the default evaluation context that is used in our templates to be able to do this.
I want to de-clutter our Thymeleaf HTML templates by replacing this sort of thing:
T(org.apache.commons.io.FileUtils).byteCountToDisplaySize(1024)
With:
T(FileUtils).byteCountToDisplaySize(1024)
I tried autowiring an EvaluationContext into a controller to see if I could get hold of it, but Spring tells me no qualifying beans are found. Any advice appreciated!
I'm using JAVA based spring config. So in the spring security config class (There must be a #EnableGlobalMethodSecurity(prePostEnabled = true) annotation) , I'm injecting a custom MethodSecurityExpressionHandler bean:
#Bean
public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler() {
#Override
public StandardEvaluationContext createEvaluationContextInternal(final Authentication auth, final MethodInvocation mi) {
StandardEvaluationContext evaluationContext = super.createEvaluationContextInternal(auth, mi);
//Register custom package paths, since StandardTypeLocator is only aware of "java.lang"
//So there will be no need to provide a fully qualified path
((StandardTypeLocator) evaluationContext.getTypeLocator()).registerImport("desired.path");
return evaluationContext;
}
};
expressionHandler.setPermissionEvaluator(new ExpressionAccessPermissionEvaluator()); //register some custom PermissionEvaluator if any
return expressionHandler;
}
}
Thanks
I am not sure if this solves your problem, but ThymeleafViewResolver class has addStaticVariable method which adds variables to the context before the view is processed.
I made a little test:
#Autowired
ThymeleafViewResolver thymeleafViewResolver;
#PostConstruct
public void postConstruct() {
thymeleafViewResolver.addStaticVariable("myUtil", new StringUtils());
}
With StringUtils like the below:
public class StringUtils {
public static String print() {
return "Printed";
}
}
And the view:
<div th:text="${T(some.package.StringUtils).print()}">Test</div>
<div th:text="${myUtil.print()}">Test</div>
Both worked fine. The latter will also work if your method is not static.
Hope it helps.
I managed to do this by wrapping the EngineContextFactory Class so I put as follow in my Thymeleaf config class:
#Bean
public SpringTemplateEngine springTemplateEngine(SpringResourceTemplateResolver templateResolver,
IDialect springSecurityDialect)
{
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
templateEngine.setEngineContextFactory(engineContextFactory());
templateEngine.addDialect(springSecurityDialect);
return templateEngine;
}
private IEngineContextFactory engineContextFactory()
{
return new EngineContextFactoryWrapper()
// packages to register
.registerImport("java.util")
.registerImport("java.math")
.registerImport("com.mainsys.fhome.gui.util");
}
public static class EngineContextFactoryWrapper
implements IEngineContextFactory
{
private final IEngineContextFactory delegate;
private final List<String> typeLocatorPrefixes;
public EngineContextFactoryWrapper()
{
super();
delegate = new StandardEngineContextFactory();
typeLocatorPrefixes = new ArrayList<String>();
}
#Override
public IEngineContext createEngineContext(IEngineConfiguration configuration,
TemplateData templateData,
Map<String, Object> templateResolutionAttributes,
IContext context)
{
IEngineContext engineCtx = delegate.createEngineContext(configuration, templateData, templateResolutionAttributes, context);
EvaluationContext evaluationContext;
if (engineCtx.containsVariable(ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME))
{
evaluationContext = (EvaluationContext) engineCtx.getVariable(ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME);
}
else
{
evaluationContext = new ThymeleafEvaluationContextWrapper(new StandardEvaluationContext());
}
for (String prefix : typeLocatorPrefixes)
{
((StandardTypeLocator) evaluationContext.getTypeLocator()).registerImport(prefix);
}
return engineCtx;
}
public EngineContextFactoryWrapper registerImport(String prefix)
{
typeLocatorPrefixes.add(prefix);
return this;
}
}