복붙노트

[SPRING] 프로세스 봄 부팅 외부화 된 속성 값

SPRING

프로세스 봄 부팅 외부화 된 속성 값

필자의 설정 파일에서 암호를 모호하게 다루는 작업이 있습니다. 이것이 올바른 접근 방식이라고 생각하지 않지만 관리자들은 동의하지 않습니다 ...

그래서 내가하고있는 프로젝트는 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)

이 작업을 수행하기 위해 정규 표현식을 사용하여 속성 값을 구문 분석하고 일치하는 경우 값을 난독 화 / 암호 해독 값으로 바꿉니다.

하지만 재산 가치를 가로막는 방법은 무엇입니까?

해결법

  1. ==============================

    1.마침내 이것이 작동하게되면. (주로 github의 stephane-deraco 덕분에)

    마침내 이것이 작동하게되면. (주로 github의 stephane-deraco 덕분에)

    솔루션의 핵심은 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();
        }
    
    }
    

    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가이 단계에서 초기화되지 않았기 때문에 autowiring이나 다른 bean 관련 메커니즘이 작동하지 않을 것이다.

    업데이트 : @ jny 님의 제안이 포함되었습니다.

  2. ==============================

    2.https://github.com/ulisesbocchio/jasypt-spring-boot를 사용하면 바로 사용할 수 있습니다.

    https://github.com/ulisesbocchio/jasypt-spring-boot를 사용하면 바로 사용할 수 있습니다.

  3. ==============================

    3.@Daniele Torino의 대답을 사용하고 몇 가지 사소한 변경을했습니다.

    @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는 CompositePropertySource가 EnumerablePropertySource에서 상속되기 때문에 if (source instanceof CompositePropertySource) {가 중복되는 경우입니다.

    셋째, 사소한 버그가 있다고 생각합니다. 속성 해결의 순서가 엉망입니다. 환경에 하나의 인코딩 된 속성이 있고 application.properties 파일에 또 다른 속성이있는 경우 환경 값은 application.properties 값으로 덮어 씁니다. 나는 encoded 전에 바로 encodedProperties를 삽입하는 로직을 변경했다 :

            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));
                    }
            }
    
  4. ==============================

    4.@gogstad에서 영감을 얻었습니다. 다음은 스프링 부트 프로젝트에서 내 사용자 이름과 암호를 암호화하고 tomcat을 사용하여 프로젝트에서 해독 한 주요 작업입니다.

    @gogstad에서 영감을 얻었습니다. 다음은 스프링 부트 프로젝트에서 내 사용자 이름과 암호를 암호화하고 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
    
  5. from https://stackoverflow.com/questions/31989883/process-spring-boot-externalized-property-values by cc-by-sa and MIT license