Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a custom Jasypt PropertySource in Springboot

I'm using Spring Boot to create a simple web application which accesses a database. I'm taking advantage of the autoconfiguration functionality for the DataSource by setting up spring.datasource.* properties in application.properties. That all works brilliantly and was very quick - great work guys @ Spring!

My companys policy is that there should be no clear text passwords. Therefore I need to have the sping.datasource.password encrypted. After a bit of digging around I decided to create a org.springframework.boot.env.PropertySourceLoader implementation which creates a jasypt org.jasypt.spring31.properties.EncryptablePropertiesPropertySource as follows:

public class EncryptedPropertySourceLoader implements PropertySourceLoader
{
    private final StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();

    public EncryptedPropertySourceLoader()
    {
        //TODO: this could be taken from an environment variable
        this.encryptor.setPassword("password"); 
    }

    @Override
    public String[] getFileExtensions()
    {
        return new String[]{"properties"};
    }

    @Override
    public PropertySource<?> load(final String name, final Resource resource, final String profile) throws IOException
    {
        if (profile == null)
        {
            final Properties props = PropertiesLoaderUtils.loadProperties(resource);

            if (!props.isEmpty())
            {
                return new EncryptablePropertiesPropertySource(name, props, this.encryptor);
            }
        }

        return null;
    }
}

I then packaged this in it's own jar with a META-INF/spring.factories file as follows:

org.springframework.boot.env.PropertySourceLoader=com.mycompany.spring.boot.env.EncryptedPropertySourceLoader

This works perfectly when run from maven using mvn spring-boot:run. The problem occurs when I run it as a standalone war using java -jar my-app.war. The application still loads but fails when I try to connect to the database as the password value is still encrypted. Adding logging reveals that the EncryptedPropertySourceLoader is never loaded.

To me this sounds like a classpath issue. When run under maven the jar loading order is strict but once under the embebed tomcat there is nothing to say that my custom jar should be loaded before Spring Boot.

I've tried adding the following to my pom.xml to ensure the classpth is preserved but it doesn't seem to have had any effect.

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                    <archive>
                        <manifest>
                            <mainClass>${start-class}</mainClass>
                            <addClasspath>true</addClasspath>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </pluginManagement>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

Does anyone have any ideas? Thanks in advance.

UPDATE:

A step forward: I've managed to fix this by having the EncryptedPropertySourceLoader class implement org.springframework.core.PriorityOrdered interface and returning HIGHEST_PRECEDENCE from getOrder(). This has now fixed the issue of the PropertySourceLoader not being used. However it's now throwing the following error when it tries to decrypt the properties:

org.jasypt.exceptions.EncryptionInitializationException: java.security.NoSuchAlgorithmException: PBEWithMD5AndDES SecretKeyFactory not available
    at org.jasypt.encryption.pbe.StandardPBEByteEncryptor.initialize(StandardPBEByteEncryptor.java:716)
    at org.jasypt.encryption.pbe.StandardPBEStringEncryptor.initialize(StandardPBEStringEncryptor.java:553)
    at org.jasypt.encryption.pbe.StandardPBEStringEncryptor.decrypt(StandardPBEStringEncryptor.java:705)
    at org.jasypt.properties.PropertyValueEncryptionUtils.decrypt(PropertyValueEncryptionUtils.java:72)
    at org.jasypt.properties.EncryptableProperties.decode(EncryptableProperties.java:230)
    at org.jasypt.properties.EncryptableProperties.get(EncryptableProperties.java:209)
    at org.springframework.core.env.MapPropertySource.getProperty(MapPropertySource.java:36)
    at org.springframework.boot.env.EnumerableCompositePropertySource.getProperty(EnumerableCompositePropertySource.java:49)
    at org.springframework.boot.context.config.ConfigFileApplicationListener$ConfigurationPropertySources.getProperty(ConfigFileApplicationListener.java:490)

Again this doesn't happen when running from mvn spring-boot:run but does happen when running from the executable war file. Both scenarios use the same JVM (jdk1.6.0_35). Results on Google/Stackoverflow suggest this is an issue with the java security policy but as it does work when run from maven I think I can discount that. Possibly a packaging issue...

like image 898
Mudged Avatar asked Jun 27 '14 11:06

Mudged


People also ask

How do I decrypt with Jasypt?

The required steps to use it are: Create an instance (using new). Set a password (using setPassword(String) or setPasswordCharArray(char[])). Perform the desired encrypt(String) or decrypt(String) operations.


2 Answers

There are two issues here.

1) The EncryptedPropertySourceLoader needs to be loaded higher than then standard PropertiesPropertySourceLoader. This can be achieved by implementing the PriorityOrder interface as follows:

public class EncryptedPropertySourceLoader implements PropertySourceLoader, PriorityOrdered
{
    private final StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();

    public EncryptedPropertySourceLoader()
    {
        this.encryptor.setPassword("password"); //TODO: this could be taken from an environment variable
    }

    @Override
    public String[] getFileExtensions()
    {
        return new String[]{"properties"};
    }

    @Override
    public PropertySource<?> load(final String name, final Resource resource, final String profile) throws IOException
    {
        if (profile == null)
        {
            //load the properties
            final Properties props = PropertiesLoaderUtils.loadProperties(resource);

            if (!props.isEmpty())
            {
                //create the encryptable properties property source
                return new EncryptablePropertiesPropertySource(name, props, this.encryptor);
            }
        }

        return null;
    }

    @Override
    public int getOrder()
    {
        return HIGHEST_PRECEDENCE;
    }
}

The org.springframework.core.io.support.SpringFactoriesLoader class which loads the org.springframework.boot.env.PropertySourceLoader from the META-INF/spring.factories orders the results using org.springframework.core.OrderComparator. Meaning that this class should be returned first and will be given the responsibility of providinging the PropertySourceLoader implementation for *.proerpties files.

2) The second is a class loading issue with the executable JAR/WAR which seems to be caused by a bug in version 1.1.2.RELEASE of Spring Boot on Windows. Dropping to version 1.1.1.RELEASE or to version 1.1.3.RELEASE solves the various issues with classes and proerpties file not being loaded when run outside of maven.

like image 133
Mudged Avatar answered Oct 12 '22 20:10

Mudged


You could give this a try: jasypt-spring-boot It basically wraps all PropertySource present in the Environment with an encryptable version. The 2 things you gotta do once you import the library (adding the dependency if you use maven) is to annotate your @Configuration class with @EnableEncryptableProperties, and to configure the encryption algorithm and password through properties.

like image 29
Ulises Avatar answered Oct 12 '22 19:10

Ulises