【问题标题】:Process Spring Boot externalized property values处理 Spring Boot 外部化属性值
【发布时间】:2015-11-06 12:26:51
【问题描述】:

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

所以我正在做的项目是基于 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)

为了使其工作,我想使用正则表达式解析属性值,如果匹配,则将值替换为去混淆/解密的值。

但是如何截取属性值呢?

【问题讨论】:

  • 我不认为你可以,如果你只是使用 Spring 内置的东西。但是,如果您手动加载此 YAML,然后将其输入 Spring,请在此处执行。
  • 我想我可以编写自己的 PropertySource。我希望找到一种适用于所有财产来源的机制。
  • 您可能对this Spring Boot issue感兴趣
  • 感谢您的链接,看起来很有希望。明天我会尝试一些建议的方法。

标签: spring spring-boot


【解决方案1】:

如果终于让它工作了。 (主要感谢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 相关机制将不起作用。


更新:包含@jny的建议。

【讨论】:

  • 我注意到您使用 apache 版本的 base64 而不是 github.com/spring-projects/spring-boot/… 中的代码,为什么会这样?
  • 基本上我并不关心使用什么实现。我们已经使用了 apache commons 库,所以我没有注意到 spring boot 中也有一个。
【解决方案2】:

我使用了@Daniele Torino 的答案并做了一些小改动。

首先,感谢他对如何让 spring 识别 Initializer 的选项的链接,我选择在 Application 中进行操作:

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

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

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

【讨论】:

  • 我已更新答案以包含您的建议。您的答案似乎有一个小错误,您创建了两个 MapPropertySource - 第二个以 decodedProperties 作为参数。该参数应为Map,但为PropertySource&lt;?&gt;
【解决方案3】:

【讨论】:

    【解决方案4】:

    灵感来自@gogstad。这是我在spring boot项目中加密我的用户名和密码并在项目中解密它们以使用tomcat的主要操作:

    1.在 pom.xml 文件中

        <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。 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.加密你的用户名和密码,并在 application.properties 文件中填入结果:

        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.在 src/main/resources/config 目录下添加两个属性文件:

        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
    

    【讨论】:

    • OP 想要编码,而不是加密。
    【解决方案5】:

    使用spring cloud config server

    定义 encrypt.key=MySecretKey

    发消息加密https://config-server/encrypt

    现在定义密码

    app.password={cipher}encryptedvalue
    

    在代码中使用@Value("${app.password}")

    spring boot 应该会给你解密后的值

    【讨论】:

    • 1.使用配置服务器是一种范式转变,而不是 OP 正在寻找的问题的解决方案。 2. 他在寻找 Base64 编码,而不是加密。
    猜你喜欢
    • 2018-02-21
    • 1970-01-01
    • 2018-02-25
    • 2021-05-17
    • 2016-08-06
    • 1970-01-01
    • 2020-07-30
    • 2019-06-12
    • 2017-10-26
    相关资源
    最近更新 更多