Web/Spring 2020. 4. 29. 20:50

 

오늘 다루어볼 내용은 spring application.yaml(properties)파일들의 로드 규칙 및 순서이다. 기본적인 내용일수는 있겠지만 필자는 이번에 해당 순서의 중요성을 다시 한번 알게되서 한번더 정리해보려고 한다.

 

 

Spring Boot Features

If you need to call remote REST services from your application, you can use the Spring Framework’s RestTemplate class. Since RestTemplate instances often need to be customized before being used, Spring Boot does not provide any single auto-configured RestT

docs.spring.io

 

위 링크를 보면 PropertySource의 적용 순서가 나와있다. 우리가 신경 쓸 것은 application.properties와 application-{profiles}.properties의 순서이다. 위 링크에서는 application-{profiles}.properties가 더 우선 순위를 갖는다고 이야기하고 있다. 그 말은 무엇일까? 아래 간단히 application.properties 파일을 확인해보자.

 

#application.yaml
spring:
  application:
    name: name-1
    
#application-dev.yaml
spring:
  application:
    name: name-2

 

과연 위 yaml파일들을 모두 적용하고 나서 아래 코드를 실행한다면 어떤 값이 출력될까? 실행할때 VM option에

 

"-Dspring.profiles.active=dev"

 

을 넣어서 run 해야한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Slf4j
@RestController
@SpringBootApplication
public class JenkinsSampleApplication implements CommandLineRunner {
 
    @Value("${spring.application.name}")
    private String appName;
 
    public static void main(String[] args) {
        SpringApplication.run(JenkinsSampleApplication.class, args);
    }
 
    @Override
    public void run(String... args) throws Exception {
        log.info(appName);
    }
 
}
cs

 

결과는 "name-2"가 출력된다. 그 말은 application-{profiles}.yaml이 우선 적용이 된다는 뜻이다. 그렇다면 아래와 같은 config가 있다면 어떨까?

 

#application.yaml
spring:
  application:
    name: name-1
    
#application-dev.yaml
spring:
  profiles:
    include: dev-common
  application:
    name: name-2
    
#application-dev-common.yaml
spring:
  application:
    name: name-3

 

출력결과는 "name-3"이다. 그 말은 application-dev.yaml에서 include한 application-dev-common.yaml이 가장 큰 우선순위를 갖는 것이다. 

 

#application.yaml
spring:
  profiles:
    include: dev-common
  application:
    name: name-1
    
#application-dev.yaml
spring:
  application:
    name: name-2
    
#application-dev-common.yaml
spring:
  application:
    name: name-3

 

위 파일은 application-dev-common.yaml파일을 application.yaml에 include하였다. 이때 결과는 어떻게 될것인가? "name-2"를 출력할 것이다. 그 이유는 application.yaml에서 include했지만 application-dev.yaml이 더 우선순위를 갖기 때문이다. 즉, include된 설정값이 가장 우선순위를 갖기 위해서는 application-{profiles}.yaml에 include해야한다.

(만약 application-{profiles}.yaml이 없었다면 application-dev-common.yaml의 설정인 "name-3" 설정이 적용이 될것이다.)

 

해당 규칙들을 잘 활용해서 관리하기 쉽게 설정값들을 유지하면 좋을 것 같다. 여기까지 간단하게 spring application 설정의 적용 규칙 및 순서에 대해 다루어보았다.

posted by 여성게
:
Web/Spring 2019. 8. 5. 14:43

 

이번 포스팅에서 다루어볼 내용은 spring boot project에서 DB Access관련 설정을 application.properties에 설정 해 놓는데, 기존처럼 평문으로 username/password를 넣는 것이 아니라 특정 알고리즘으로 암호화된 문자열을 넣고 애플리케이션 스타트업 시점에 복호화하여 DB Access를 하는 것입니다. 바로 예제로 들어가겠습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.example.demo;
 
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
 
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
 
import org.junit.Test;
 
public class EncryptTest {
 
    @Test
    public void encrypt() throws Exception {
        
        String str = "ENCRYPT!@1234";
        String encStr = encAES(str);
        System.out.println(encStr);
        System.out.println(decAES(encStr));
        
    }
    
    public Key getAESKey() throws Exception {
        String iv;
        Key keySpec;
 
        String key = "encryption!@1234";
        iv = key.substring(016);
        byte[] keyBytes = new byte[16];
        byte[] b = key.getBytes("UTF-8");
 
        int len = b.length;
        if (len > keyBytes.length) {
           len = keyBytes.length;
        }
 
        System.arraycopy(b, 0, keyBytes, 0, len);
        keySpec = new SecretKeySpec(keyBytes, "AES");
 
        return keySpec;
    }
 
    // 암호화
    public String encAES(String str) throws Exception {
        Key keySpec = getAESKey();
        String iv = "0987654321654321";
        Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
        c.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(iv.getBytes("UTF-8")));
        byte[] encrypted = c.doFinal(str.getBytes("UTF-8"));
        String enStr = new String(Base64.getEncoder().encode(encrypted));
 
        return enStr;
    }
 
    // 복호화
    public String decAES(String enStr) throws Exception {
        Key keySpec = getAESKey();
        String iv = "0987654321654321";
        Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
        c.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv.getBytes("UTF-8")));
        byte[] byteStr = Base64.getDecoder().decode(enStr.getBytes("UTF-8"));
        String decStr = new String(c.doFinal(byteStr), "UTF-8");
 
        return decStr;
    }
}
 
cs

 

위의 소스는 암복호화 단위테스트 코드입니다. AES-128 방식으로 암복호화하였으며 CBC 방식으로 진행하였습니다. 암호화 알고리즘에 대해서는 추후 포스팅하도록 하겠습니다.

 

1
2
3
#AES128_Encrypt
spring.datasource.username=T7Me97L7JW9YXubNVtNfpQ==
spring.datasource.password=/Wm/Ubs7CniQuA+5Mzq7Qg==
cs

 

위는 암호화된 형태소 Datasource 정보를 넣은 것입니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package com.example.demo.config;
 
import java.security.Key;
import java.util.Base64;
import java.util.Properties;
 
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertiesPropertySource;
 
public class EncryptEnvPostProcessor implements EnvironmentPostProcessor {
 
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        
        Properties props = new Properties();
        try {
            props.put("spring.datasource.password", decAES(environment.getProperty("spring.datasource.password")));
            props.put("spring.datasource.username", decAES(environment.getProperty("spring.datasource.username")));
        } catch (Exception e) {
            System.out.println("DB id/password decrypt fail !");
        }
 
        environment.getPropertySources().addFirst(new PropertiesPropertySource("myProps", props));
 
    }
    
    public Key getAESKey() throws Exception {
        String iv;
        Key keySpec;
 
        String key = "encryption!@1234";
        iv = key.substring(016);
        byte[] keyBytes = new byte[16];
        byte[] b = key.getBytes("UTF-8");
 
        int len = b.length;
        if (len > keyBytes.length) {
           len = keyBytes.length;
        }
 
        System.arraycopy(b, 0, keyBytes, 0, len);
        keySpec = new SecretKeySpec(keyBytes, "AES");
 
        return keySpec;
    }
 
    // 암호화
    public String encAES(String str) throws Exception {
        Key keySpec = getAESKey();
        String iv = "0987654321654321";
        Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
        c.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(iv.getBytes("UTF-8")));
        byte[] encrypted = c.doFinal(str.getBytes("UTF-8"));
        String enStr = new String(Base64.getEncoder().encode(encrypted));
 
        return enStr;
    }
 
    // 복호화
    public String decAES(String enStr) throws Exception {
        Key keySpec = getAESKey();
        String iv = "0987654321654321";
        Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
        c.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv.getBytes("UTF-8")));
        byte[] byteStr = Base64.getDecoder().decode(enStr.getBytes("UTF-8"));
        String decStr = new String(c.doFinal(byteStr), "UTF-8");
 
        return decStr;
    }
 
}
 
cs

 

위 소스는 springboot application 기동 시점에 암호화된 username/password를 복호화하여 Datasource 객체를 생성하기 위한 후처리 프로세서입니다. EnvironmentPostProcessor라는 인터페이스를 구현한 클래스를 하나 생성하여 오버라이딩된 메소드에 복호화하는 로직을 구현해주시면 됩니다.

 

여기서 중요한 것은 그냥 후처리 프로세서를 등록하면 끝나는 것이 아니라 설정 파일을 넣어 주어야 합니다.

 

 

src/main/resources/META-INF/spring.factories 파일을 생성하여 아래와 같은 정보를 기입해줍니다.

 

1
2
# post processor 적용
org.springframework.boot.env.EnvironmentPostProcessor=com.example.demo.config.EncryptEnvPostProcessor
cs

EncryptEnvPostProcessor라는 클래스의 패키지네임을 포함한 Full Path를 해당 설정파일에 기입해주시면 됩니다.

 

여기까지 application.properties에 암호화된 정보를 넣어 복호화하는 예제를 진행해봤습니다. 사실 Datasource 외에도 다른 설정파일에도 적용가능한 예제입니다. 활용하시길 바랍니다!

posted by 여성게
: