首页 文章

处理Spring Boot外部化属性值

提问于
浏览
8

我的任务是在配置文件中混淆密码 . 虽然我不认为这是正确的方法,但经理不同意......

所以我正在开发的项目基于Spring Boot,我们正在使用YAML配置文件 . 目前密码是纯文本的:

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

我们的想法是使用一些支持模糊或加密密码的特殊语法:

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

为了使其工作,我想使用正则表达式解析属性值,如果匹配,则将值替换为deobfuscated / decrypted值 .

But how do I intercept the property value?

4 回答

  • 11

    如果最终得到这个工作 . (主要感谢stephane-deracogithub

    解决方案的关键是实现 ApplicationContextInitializer<ConfigurableApplicationContext> 的类 . 我叫它 PropertyPasswordDecodingContextInitializer .

    主要问题是让 Spring 天使用这个 ApplicationContextInitializer . 重要信息可在reference中找到 . 我选择使用META-INF / spring.factories的方法,内容如下:

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

    PropertyPasswordDecodingContextInitializer 使用 PropertyPasswordDecoder 和一个实现类,目前为简单起见 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);
            }
        }
    
    
    }
    

    请注意,ApplicationContext尚未在此阶段完成初始化,因此自动装配或任何其他与bean相关的机制将无法正常工作 .


    Update: 包括@ jny的建议 .

  • 3
  • 4

    我使用@ Daniele Torino的答案并进行了一些小改动 .

    首先,由于他链接到如何使 spring 识别初始化程序的选项,我选择在_1047128中执行:

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

    其次,IDEA告诉我, else if (source instanceof CompositePropertySource) { 是多余的,因为 CompositePropertySource 继承自 EnumerablePropertySource .

    第三,我相信有一个小错误:它弄乱了 property 解决的顺序 . 如果在环境中有一个编码属性,而在 application.properties 文件中有另一个,则环境值将被 application.properties 值覆盖 . 我改变了逻辑,在编码之前插入decodeProperties:

    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));
                    }
            }
    
  • 2

    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 (注意:要在tomcat上部署解密的springboot,你应该添加@ServletComponentScan注释并扩展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
    

    输出就像下面的演示:

    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
    

相关问题