001package com.poscoict.app.job; 002 003import com.poscoict.glueframework.util.GlueCipher; 004import org.slf4j.Logger; 005import org.slf4j.LoggerFactory; 006import org.springframework.beans.BeansException; 007import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 008import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; 009import org.springframework.core.env.*; 010import org.springframework.core.io.Resource; 011import org.springframework.util.StringUtils; 012 013import javax.crypto.BadPaddingException; 014import javax.crypto.IllegalBlockSizeException; 015import javax.crypto.NoSuchPaddingException; 016import java.io.File; 017import java.io.IOException; 018import java.security.InvalidKeyException; 019import java.security.NoSuchAlgorithmException; 020import java.util.Objects; 021 022/** 023 * GluePropertySourcesPlaceholderConfigurer. 024 * GluePropertySourcesPlaceholderConfigurer 는 PropertySourcesPlaceholderConfigurer 을 확장한 클래스입니다( Spring 5.2.0 ). 025 * 026 * <pre> 027 * <b>Bean Property</b> 028 * 029 * - keyFile : (선택) keyFile 이 존재할 경우, 암호화된 property 는 복호화함. 030 * 031 * 032 * <b>예제</b><xmp> 033 * 사용 예# 1 034 * 035 * <bean class="com.poscoict.sample.GluePropertySourcesPlaceholderConfigurer"> 036 * <property name="keyFile" value="file:${CIPHER_PATH}/glue.cipher"></property> 037 * <property name="locations"> 038 * <list> 039 * <value>file:${CONFIG_PATH}/glue.properties</value> 040 * </list> 041 * </property> 042 * </bean> 043 * </xmp> 044 * </pre> 045 */ 046public class GluePropertySourcesPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer { 047 048 private final Logger logger = LoggerFactory.getLogger(getClass()); 049 private final EncryptionAwareService encryptionService = new EncryptionAwareService(); 050 private Environment env; 051 052 public GluePropertySourcesPlaceholderConfigurer() { 053 super(); 054 } 055 056 public void setKeyFile(Resource keyFile) { 057 if (keyFile.exists() && keyFile.isFile()) { 058 try { 059 this.encryptionService.keyFile = keyFile.getFile(); 060 } catch (IOException e) { 061 this.logger.warn(e.getMessage(), e); 062 } 063 } else { 064 //암호화 사용되지 않는 경우가 많으니 로그 출력 하지 않음. 065 //this.logger.warn("file not exist - " + keyFile); 066 } 067 } 068 069 @Override 070 public void setEnvironment(Environment environment) { 071 this.env = environment; 072 } 073 074 @Override 075 protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, ConfigurablePropertyResolver propertyResolver) throws BeansException { 076 PropertySources propertySources = ((ConfigurableEnvironment) env).getPropertySources(); 077 078 // 여기서부터는 super.getAppliedPropertySources() 를 호출할 수 없어서.. 079 // PropertySourcesPlaceholderConfigurer의 postProcessBeanFactory()의 코드를 가져왔습니다. 080 try { 081 PropertySource<?> localPropertySource = new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, super.mergeProperties()); 082 if (this.localOverride) { 083 ((MutablePropertySources) propertySources).addFirst(localPropertySource); 084 } else { 085 ((MutablePropertySources) propertySources).addLast(localPropertySource); 086 } 087 } catch (IOException e) { 088 this.logger.error(e.getMessage(), e); 089 } 090 091 // 암호화여부를 체크해서 복호화를 진행합니다. 092 super.processProperties(beanFactoryToProcess, new EncryptionAwarePropertySourcesPropertyResolver(propertySources, encryptionService)); 093 } 094 095 /** 096 * 암호화된 문자열(property)인지 확인해서 복호화하는 서비스입니다. 097 */ 098 public class EncryptionAwareService { 099 private final Logger logger = LoggerFactory.getLogger(getClass()); 100 public static final String ENCRIPT_PREFIX = "enc##"; 101 private File keyFile = null; 102 103 public String tryDecrypt(String text) { 104 if (canTxDecrypted(text)) { 105 try { 106 return GlueCipher.decrypt(text.split(ENCRIPT_PREFIX)[1], keyFile); 107 } catch (BadPaddingException | IllegalBlockSizeException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) { 108 this.logger.error(e.getMessage(), e); 109 } 110 } 111 return text; 112 } 113 114 boolean canTxDecrypted(String text) { 115 return keyFile != null && keyFile.isFile() && StringUtils.hasText(ENCRIPT_PREFIX) 116 && text.startsWith(ENCRIPT_PREFIX) && !text.endsWith(ENCRIPT_PREFIX); 117 } 118 } 119 120 /** 121 * Property의 문자열을 resolver합니다. 122 */ 123 public class EncryptionAwarePropertySourcesPropertyResolver extends PropertySourcesPropertyResolver { 124 private final EncryptionAwareService encryptionAwareService; 125 126 public EncryptionAwarePropertySourcesPropertyResolver(PropertySources propertySources, EncryptionAwareService encryptionAwareService) { 127 super(propertySources); 128 this.encryptionAwareService = encryptionAwareService; 129 } 130 131 @Override 132 public String resolvePlaceholders(String text) { 133 String resolvedText = super.resolvePlaceholders(text); 134 if (Objects.isNull(encryptionAwareService)) 135 return resolvedText; 136 return encryptionAwareService.tryDecrypt(resolvedText); 137 } 138 139 @Override 140 public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException { 141 String resolvedText = super.resolveRequiredPlaceholders(text); 142 if (Objects.isNull(encryptionAwareService)) 143 return resolvedText; 144 return encryptionAwareService.tryDecrypt(resolvedText); 145 } 146 } 147}