进程Spring引导外部化属性值

进程Spring引导外部化属性值,spring,spring-boot,Spring,Spring Boot,我的任务是混淆配置文件中的密码。虽然我认为这不是正确的方法,但经理们不同意 因此,我正在从事的项目是基于Spring Boot的,我们正在使用YAML配置文件。当前密码为纯文本: spring: datasource: url: jdbc:sqlserver://DatabaseServer driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver username: ele

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

因此,我正在从事的项目是基于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)
为了实现这一点,我希望使用正则表达式解析属性值,如果它匹配,则将该值替换为deobfouscated/decrypted值


但是如何截取属性值呢?

如果最终实现了这一点。(主要归功于on)

解决方案的关键是实现
ApplicationContextInitializer
的类。我称之为
PropertyPasswordDecodingContextInitializer

主要问题是让spring使用这个
ApplicationContextInitializer
。重要信息可在中找到。我选择了使用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();
    }

}
package ch.mycompany.myproject;

public interface PropertyPasswordDecoder {

    public String decodePassword(String encodedPassword);

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


}
Base64PropertyPasswordDecoder.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();
    }

}
package ch.mycompany.myproject;

public interface PropertyPasswordDecoder {

    public String decodePassword(String encodedPassword);

}
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相关的机制将无法工作


更新:包含了@的建议。

我使用了@的答案,并做了一些小改动

首先,由于他链接到如何使spring识别初始值设定项的选项,我选择在
应用程序中执行此操作:

public static void main(String[] args) throws Exception {
    SpringApplication application=new SpringApplication(Application.class);
    application.addInitializers(new PropertyPasswordDecodingContextInitializer());
    application.run(args);
}
其次,IDEA告诉我,
else if(CompositePropertySource的源实例){
是冗余的,这是因为
CompositePropertySource
继承自
EnumerablePropertySource

第三,我认为有一个小错误:它扰乱了属性解析的顺序。如果您在环境中有一个编码属性,而在
应用程序.properties
文件中有另一个编码属性,则环境值将被
应用程序.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));
                }
        }
for(PropertySource PropertySource:environment.getPropertySources()){
Map propertyOverrides=新建LinkedHashMap();
解码密码(propertySource、propertyOverrides);
如果(!propertyOverrides.isEmpty()){
environment.getPropertySources().addBefore(propertySource.getName(),新映射propertySource(“decoded”+propertySource.getName(),propertyOverrides));
}
}
只需使用,开箱即用

灵感来自@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>
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
    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
输出如下图所示:

4.在src/main/resources/config目录下添加两个属性文件:

    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
    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

使用spring云配置服务器

定义encrypt.key=MySecretKey

发布要加密的消息

现在定义密码就像

app.password={cipher}encryptedvalue
在代码中使用
@Value(“${app.password}”)


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

如果你只是在使用spring内置的东西,我认为你不能。但是,如果你手动加载这个YAML,然后将它输入spring,那么就在那里进行。我想我可以写我自己的PropertySource。我希望找到一种适用于所有属性源的机制。你可能是inte感谢您的链接,看起来很有希望。我明天将尝试一些建议的方法。我注意到您在《为什么是这样?》中使用了apache版本的base64,而不是您的代码。基本上,我并不关心要使用什么实现。我们已经使用了apache commons库,所以我没有注意到spring boot中也有一个。我ave更新了答案以包含您的建议。您的答案似乎有一个小错误,您创建了两个
MapPropertySource
——第二个以
decodedProperties
为参数。该参数应该是
Map
,但却是
PropertySource
。1.使用配置服务器是一种范式转换,而不是OP正在寻找问题的解决方案。2.他在寻找Base64编码,而不是加密。OP想要的是编码,而不是加密。