Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
196 views
in Technique[技术] by (71.8m points)

Process Spring Boot externalized property values

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 working on is based on Spring Boot and we are using YAML configuration files. Currently the passwords are in plain text:

spring:
    datasource:
        url: jdbc:sqlserver://DatabaseServer
        driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
        username: ele
        password: NotTheRealPassword

The idea is to have some special syntax that supports an obfuscated or encrypted password:

spring:
    datasource:
        url: jdbc:sqlserver://DatabaseServer
        driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
        username: ele
        password: password(Tm90VGhlUmVhbFBhc3N3b3Jk)

In order for this to work I want to parse the property values using a regular expression and if it matches replace the value with the deobfuscated/decrypted value.

But how do I intercept the property value?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...