Process Spring Boot externalized property values

后端 未结 5 710
广开言路
广开言路 2020-12-03 12:22

I have the task of obfuscating passwords in our configuration files. While I don\'t think this is the right approach, managers disagree...

So the project I am workin

相关标签:
5条回答
  • 2020-12-03 13:01

    I used @Daniele Torino's answer and made several minor changes.

    First, thanks to his link to the options on how to make spring recognize Initializer, I chose to do it in the Application:

    public static void main(String[] args) throws Exception {
        SpringApplication application=new SpringApplication(Application.class);
        application.addInitializers(new PropertyPasswordDecodingContextInitializer());
        application.run(args);
    }
    

    Second, IDEA told me that that else if (source instanceof CompositePropertySource) { is redundant and it is because CompositePropertySource inherits from EnumerablePropertySource.

    Third, I beleive there is a minor bug: it messes up the order of property resolution. If you have one encoded property in environment, and another one in application.properties file the environment value will be overwritten with the application.properties value. I changed the logic to insert the decodedProperties right before encoded:

            for (PropertySource<?> propertySource : environment.getPropertySources()) {
                    Map<String, Object> propertyOverrides = new LinkedHashMap<>();
                    decodePasswords(propertySource, propertyOverrides);
                    if (!propertyOverrides.isEmpty()) {
                           environment.getPropertySources().addBefore(propertySource.getName(), new MapPropertySource("decoded"+propertySource.getName(), propertyOverrides));
                    }
            }
    
    0 讨论(0)
  • 2020-12-03 13:11

    If finally got this to work. (Mainly thanks to stephane-deraco on github)

    Key to the solution is a class that implements ApplicationContextInitializer<ConfigurableApplicationContext>. I called it PropertyPasswordDecodingContextInitializer.

    The main problem was to get spring to use this ApplicationContextInitializer. Important information can be found in the reference. I chose the approach using a META-INF/spring.factories with following content:

    org.springframework.context.ApplicationContextInitializer=ch.mycompany.myproject.PropertyPasswordDecodingContextInitializer
    

    The PropertyPasswordDecodingContextInitializer uses a PropertyPasswordDecoder and an implementing class, currently for simplicity a Base64PropertyPasswordDecoder.

    PropertyPasswordDecodingContextInitializer.java

    package ch.mycompany.myproject;
    
    import java.util.LinkedHashMap;
    import java.util.Map;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    import org.springframework.context.ApplicationContextInitializer;
    import org.springframework.context.ConfigurableApplicationContext;
    import org.springframework.core.env.CompositePropertySource;
    import org.springframework.core.env.ConfigurableEnvironment;
    import org.springframework.core.env.EnumerablePropertySource;
    import org.springframework.core.env.MapPropertySource;
    import org.springframework.core.env.PropertySource;
    import org.springframework.stereotype.Component;
    
    @Component
    public class PropertyPasswordDecodingContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    
        private static final Pattern decodePasswordPattern = Pattern.compile("password\\((.*?)\\)");
    
        private PropertyPasswordDecoder passwordDecoder = new Base64PropertyPasswordDecoder();
    
        @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) {
                        String decodedValue = decodePasswordsInString((String) rawValue);
                        propertyOverrides.put(key, decodedValue);
                    }
                }
            }
        }
    
        private String decodePasswordsInString(String input) {
            if (input == null) return null;
            StringBuffer output = new StringBuffer();
            Matcher matcher = decodePasswordPattern.matcher(input);
            while (matcher.find()) {
                String replacement = passwordDecoder.decodePassword(matcher.group(1));
                matcher.appendReplacement(output, replacement);
            }
            matcher.appendTail(output);
            return output.toString();
        }
    
    }
    

    PropertyPasswordDecoder.java

    package ch.mycompany.myproject;
    
    public interface PropertyPasswordDecoder {
    
        public String decodePassword(String encodedPassword);
    
    }
    

    Base64PropertyPasswordDecoder.java

    package ch.mycompany.myproject;
    
    import java.io.UnsupportedEncodingException;
    
    import org.apache.commons.codec.binary.Base64;
    
    public class Base64PropertyPasswordDecoder implements PropertyPasswordDecoder {
    
        @Override
        public String decodePassword(String encodedPassword) {
            try {
                byte[] decodedData = Base64.decodeBase64(encodedPassword);
                String decodedString = new String(decodedData, "UTF-8");
                return decodedString;
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }
        }
    
    
    }
    

    Mind you, the ApplicationContext has not finished initialized at this stage, so autowiring or any other bean related mechanisms won't work.


    Update: Included @jny's suggestions.

    0 讨论(0)
  • 2020-12-03 13:11

    Inspired by @gogstad. Here is my major action in the spring boot project to encrypted my username and password and decrypted them in the project to work with tomcat:

    1. In pom.xml file

        <dependency>
            <groupId>com.github.ulisesbocchio</groupId>
            <artifactId>jasypt-spring-boot</artifactId>
            <version>1.12</version>
        </dependency>
        …
        <build>
            <resources>
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                    </includes>
                    <targetPath>${project.build.directory}/classes</targetPath>
                </resource>
                <resource>
                    <directory>src/main/resources</directory>
                    <includes>
                        <include>**/*.properties</include>
                    </includes>
                    <targetPath>${project.build.directory}/classes</targetPath>
            </resource>
        </resources>
        …
        </build>
    

    2. In App.java (Note:to deploy the decryted springboot on tomcat, you should add the @ServletComponentScan annotation and extends the SpringBootServletInitializer)

        @SpringBootApplication
        @ServletComponentScan
        @EnableEncryptableProperties
        @PropertySource(name="EncryptedProperties", value = "classpath:config/encrypted.properties")
        public class App extends SpringBootServletInitializer {
        public static void main(String[] args) throws Exception {
            SpringApplication.run(App.class, args);
            }
    
        }
    

    3. Encrypted your username and password and fill the application.properties file with the result:

        java -cp ~/.m2/repository/org/jasypt/jasypt/1.9.2/jasypt-1.9.2.jar  org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input="mypassword" password=mykey algorithm=PBEWithMD5AndDES
    

    output is like the demo below:

        java -cp ~/.m2/repository/org/jasypt/jasypt/1.9.2/jasypt-1.9.2.jar  org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input="mypassword" password=mykey algorithm=PBEWithMD5AndDES
    
        ----ENVIRONMENT-----------------
    
        Runtime: Oracle Corporation Java HotSpot(TM) 64-Bit Server VM 25.45-b02
    
    
    
        ----ARGUMENTS-------------------
    
        algorithm: PBEWithMD5AndDES
        input: mypassword
        password: mykey
    
    
    
        ----OUTPUT----------------------
    
        5XNwZF4qoCKTO8M8KUjRprQbivTkmI8H
    

    4. under the directory src/main/resources/config add two properties file:

        a. application.properties
            spring.datasource.driver-class-name=com.mysql.jdbc.Driver
            spring.datasource.url=jdbc:mysql://xxx
            spring.datasource.username=ENC(xxx)
            spring.datasource.password=ENC(xxx)
            mybatis.mapper-locations=classpath:*/mapper/*.xml
            mybatis.type-aliases-package=com.xx.xxx.model
            logging.level.com.xx.xxx: DEBUG
    
        b. encrypted.properties
            jasypt.encryptor.password=mykey
    
    0 讨论(0)
  • 2020-12-03 13:11

    Use spring cloud config server

    Define encrypt.key=MySecretKey

    Post message to encrypt https://config-server/encrypt

    Define password now like

    app.password={cipher}encryptedvalue
    

    Use @Value("${app.password}") in code

    and spring boot should give you decrypted value

    0 讨论(0)
  • 2020-12-03 13:15

    Just use https://github.com/ulisesbocchio/jasypt-spring-boot, works out of the box

    0 讨论(0)
提交回复
热议问题