Question

I am looking to replace the Environment bean used by Spring with my own implementation. Is this bad-practice, and if not, how can I do this cleanly? Currently I have created a bean that implements the Environment interface and uses the existing Environment bean, but this means all the configuration code needing the Environment bean must now use my custom Environment bean. I would think it would be cleaner to replace Springs Environment bean with my own, then no configuration needing it would need to change. Currently the only way I can think to do this would be to either create my own ApplicationContext thus setting the environment to my own, or to have something be ApplicationContextAware and set the environment there. Both of these seem to be a bit hokey to me.

Constraints:

  • I am using the latest version of Spring3.
  • I am using Java based configuration; not XML

Thank you.

Edit: Background

I suppose I should explain why I want to do this in case my thinking is flawed. I was avoid this in order to avoid the nonconstructive "why would you want to do that?" responses.

The Spring Environment bean, when looking for property values, uses a set of property sources. A typical stack looks like this (but is not limited to):

  • JNDI
  • System Properties (set via -Dmyprop=foo)
  • Environment Variables
  • ...

For security reason, it is necessary to encrypt some of these properties (e.g. database passwords). The go to solution is to use Jasypt for property encryption. However, the Spring/Jasypt only provide a means of inserting a new property source into the environment. So:

  • Property file with potentially encrypted values
  • JNDI
  • System Properties (set via -Dmyprop=foo)
  • Environment Variables
  • ...

However, this is not ideal as this means that properties can only be stored in a single file to be maintained by the operations group or that properties would be spread out amongst property files, environment variables, etc. In addition, I feel that properties have the potential of being encrypted regardless of their property source.

So this lead me to thinking that I either need to decrypt the properties in my code wherever I attempt to access them from the environment, or I need to create my own Environment bean that can do this for me.

I'm welcome to hear constructive comments and alternatives.

EDIT: Adding solution based on answer from M. Deinum

public class EnvironmentBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    private static final String CONFIGURATION_PROPERTY_PBE_ALGORITHM = "PBE_ALGORITHM";
    private static final String CONFIGURATION_PROPERTY_PBE_PASSWORD = "PBE_PASSWORD";

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        StandardEnvironment environment = (StandardEnvironment) beanFactory.getBean("environment");

        if (environment != null) {
            StringEncryptor encryptor = this.getEncryptor(environment);

            MutablePropertySources mutablePropertySources = environment.getPropertySources();

            for (PropertySource<?> propertySource : mutablePropertySources) {
                mutablePropertySources.replace(
                        propertySource.getName(), 
                        new EncryptablePropertySourcePropertySource(propertySource.getName(), propertySource, encryptor));
            }
        }
    }

    private StringEncryptor getEncryptor(Environment environment) {
        StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();

        String algorithm = environment.getProperty(CONFIGURATION_PROPERTY_PBE_ALGORITHM);
        if (algorithm != null) {
            encryptor.setAlgorithm(algorithm);
        }

        String password = environment.getProperty(CONFIGURATION_PROPERTY_PBE_PASSWORD);
        if (password != null) {
            encryptor.setPassword(password);
        }

        return encryptor;
    }

    private class EncryptablePropertySourcePropertySource extends PropertySource<PropertySource<?>> {

        private StringEncryptor stringEncryptor;

        private TextEncryptor textEncryptor;

        public EncryptablePropertySourcePropertySource(final String name, final PropertySource<?> propertySource, final StringEncryptor encryptor) {
            super(name, propertySource);

            this.stringEncryptor = encryptor;
        }

        public EncryptablePropertySourcePropertySource(final String name, final PropertySource<?> propertySource, final TextEncryptor encryptor) {
            super(name, propertySource);

            this.textEncryptor = encryptor;
        }

        @Override
        public Object getProperty(String name) {
            Object value = this.source.getProperty(name);

            if (value != null && value instanceof String) {
                value = this.decode((String) value);
            }

            return value;
        }

        private String decode(String encodedValue) {
            if (!PropertyValueEncryptionUtils.isEncryptedValue(encodedValue)) {
                return encodedValue;
            }

            if (this.stringEncryptor != null) {
                return PropertyValueEncryptionUtils.decrypt(encodedValue, this.stringEncryptor);

            }

            if (this.textEncryptor != null) {
                return PropertyValueEncryptionUtils.decrypt(encodedValue, this.textEncryptor);
            }

            throw new EncryptionOperationNotPossibleException(
                    "Neither a string encryptor nor a text encryptor exist "
                    + "for this instance of EncryptableProperties. This is usually " 
                    + "caused by the instance having been serialized and then "
                    + "de-serialized in a different classloader or virtual machine, " 
                    + "which is an unsupported behaviour (as encryptors cannot be "
                    + "serialized themselves)");
        }

    }
}
Was it helpful?

Solution

However, the Spring/Jasypt only provide a means of inserting a new property source into the environment.

Actually that is where you are wrong, you can replace PropertySources as well. See the javadoc of MutablePropertySources. This is also used internally by Spring to first add some dummy PropertySources and replace them later.

What you could do is create a PropertySource which delegates to another PropertySource and decrypts the value on the fly. That way you could replace all PropertySources with a one which wraps around the original.

MutablePropertySources mps = env.getgetPropertySources();
for (PropertySource ps : env.getgetPropertySources()) {
    EncryptablePropertySource eps = new EncryptablePropertySource(ps.getName(), ps, encryptor);
    mps.replace(ps.getName(), eps);
}

You could use the EncryptablePropertiesPropertySource as a sample, what you basically need to do is replace the Properties with PropertySource.

It would be easier if SPR-8928 would be fixed as that would allow simply for the EncryptedPropertySourcesPlaceholderConfigurer to be registered and all properties would converted and you wouldn't need these hacks.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top