Jasypt?
- Java Simplified Encryption의 약자이다.
Jasypt is a java library which allows the developer to add basic encryption capabilities to his/her projects with minimum effort, and without the need of having deep knowledge on how cryptography works
- 프로젝트에 민감한 정보를 직접적으로 노출시키지 않는 방법 중 하나
의존성
implementation("com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.5")
- 위 의존성은
jasypt-spring-boot-starter->jaspt-spring-boot->jasypt의 의존성을 갖는다.
사용
- 기본적으로 auto-configure 설정이 되어 있다. ```factory org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ulisesbocchio.jasyptspringbootstarter.JasyptSpringBootAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=com.ulisesbocchio.jasyptspringbootstarter.JasyptSpringCloudBootstrapConfiguration
com.github.ulisesbocchio:jaspyt-spring-boot-starter/META-INF/spring.factories
- `@JasyptSpringBootAutoConfiguration`를 선언으로 명시적으로 선언할 수 있다.
- 덧붙여서 String에 대해서 Encrypt할 경우 `StringEncryptor`를 선언하면 설정한 StringEncryptor를 바탕으로 encrypt, decrypt를 진행한다.
- .yaml, .yaml에 `ENC(암호문)`와 같은 형식으로 작성하면 Spring의 Bean 등록하는 과정에 Property에 대한 복호화를 진행한다.
```java
package com.ulisesbocchio.jasyptspringboot.annotation;
import com.ulisesbocchio.jasyptspringboot.configuration.EnableEncryptablePropertiesConfiguration;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({EnableEncryptablePropertiesConfiguration.class})
public @interface EnableEncryptableProperties {
}
- 위와 같이
EnableEncryptablePropertiesConfiguration를 포함하고 주입하고 있다.
@Configuration
@Import({EncryptablePropertyResolverConfiguration.class, CachingConfiguration.class})
public class EnableEncryptablePropertiesConfiguration {
private static final Logger log = LoggerFactory.getLogger(EnableEncryptablePropertiesConfiguration.class);
public EnableEncryptablePropertiesConfiguration() {
}
@Bean
public static EnableEncryptablePropertiesBeanFactoryPostProcessor enableEncryptablePropertySourcesPostProcessor(ConfigurableEnvironment environment, EncryptablePropertySourceConverter converter) {
return new EnableEncryptablePropertiesBeanFactoryPostProcessor(environment, converter);
}
}
EncryptablePropertyResolverConfiguration는 실질적으로 resolve 하는 구현에 대한 내용을 담고 있다.CachingConfiguration는 Spring Cloud에서 RefreshScope가 갱신될 때 복호화가 깨지지 않도록 재작업할 때에 대한 처리를 가지고 있다.
@Configuration
public class EncryptablePropertyResolverConfiguration {
private static final String ENCRYPTOR_BEAN_PROPERTY = "jasypt.encryptor.bean";
private static final String ENCRYPTOR_BEAN_PLACEHOLDER = String.format("${\\%s:jasyptStringEncryptor}", "jasypt.encryptor.bean");
private static final String DETECTOR_BEAN_PROPERTY = "jasypt.encryptor.property.detector-bean";
private static final String DETECTOR_BEAN_PLACEHOLDER = String.format("${\\%s:encryptablePropertyDetector}", "jasypt.encryptor.property.detector-bean");
private static final String RESOLVER_BEAN_PROPERTY = "jasypt.encryptor.property.resolver-bean";
private static final String RESOLVER_BEAN_PLACEHOLDER = String.format("${\\%s:encryptablePropertyResolver}", "jasypt.encryptor.property.resolver-bean");
private static final String FILTER_BEAN_PROPERTY = "jasypt.encryptor.property.filter-bean";
private static final String FILTER_BEAN_PLACEHOLDER = String.format("${\\%s:encryptablePropertyFilter}", "jasypt.encryptor.property.filter-bean");
private static final String ENCRYPTOR_BEAN_NAME = "lazyJasyptStringEncryptor";
private static final String DETECTOR_BEAN_NAME = "lazyEncryptablePropertyDetector";
private static final String CONFIG_SINGLETON = "configPropsSingleton";
public static final String RESOLVER_BEAN_NAME = "lazyEncryptablePropertyResolver";
public static final String FILTER_BEAN_NAME = "lazyEncryptablePropertyFilter";
public EncryptablePropertyResolverConfiguration() {
}
/*
* EnableEncryptablePropertiesBeanFactoryPostProcessor의
* postProcessBeanFactory에서 사용하는 EncryptablePropertySourceConverter가
* 여기에서 생성됩니다.
*/
@Bean
public static EncryptablePropertySourceConverter encryptablePropertySourceConverter(ConfigurableEnvironment environment, @Qualifier("lazyEncryptablePropertyResolver") EncryptablePropertyResolver propertyResolver, @Qualifier("lazyEncryptablePropertyFilter") EncryptablePropertyFilter propertyFilter) {
boolean proxyPropertySources = (Boolean)environment.getProperty("jasypt.encryptor.proxy-property-sources", Boolean.TYPE, false);
List<String> skipPropertySources = (List)environment.getProperty("jasypt.encryptor.skip-property-sources", List.class, Collections.EMPTY_LIST);
List<Class<PropertySource<?>>> skipPropertySourceClasses = (List)skipPropertySources.stream().map(EncryptablePropertySourceConverter::getPropertiesClass).collect(Collectors.toList());
InterceptionMode interceptionMode = proxyPropertySources ? InterceptionMode.PROXY : InterceptionMode.WRAPPER;
return new EncryptablePropertySourceConverter(interceptionMode, skipPropertySourceClasses, propertyResolver, propertyFilter);
}
@Bean(
name = {"lazyJasyptStringEncryptor"}
)
public StringEncryptor stringEncryptor(EnvCopy envCopy, BeanFactory bf) {
String customEncryptorBeanName = envCopy.get().resolveRequiredPlaceholders(ENCRYPTOR_BEAN_PLACEHOLDER);
//우리가 Bean으로 등록한 jasyptStringEncryptor가 여기에서 쓰인다.
boolean isCustom = envCopy.get().containsProperty("jasypt.encryptor.bean");
return new DefaultLazyEncryptor(envCopy.get(), customEncryptorBeanName, isCustom, bf);
}
}
EnableEncryptablePropertiesConfiguration에서@Bean으로 만든EnableEncryptablePropertiesBeanFactoryPostProcessor를 바탕으로 기존 PropertySource를 래핑한다. ```java //
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.ulisesbocchio.jasyptspringboot.configuration;
import com.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
/**
- BeanFactoryPostProcessor을 implements 해서
- ApplicationContext 초기화 -> Environment, PropertySource 생성 이후
- BeanDefinition으로 Bean 생성하기 전 단계에서 조작을 시작한다.
- 추가로
- BeanDefinition은 Bean에 대한 명세를 들고 있는 메타 정보를 만드는 시기
- BeanFactoryPostProcessor는 Bean Instantiate하기 전
-
BeanPostProcessor는 bean을 생성한 이후 시기 주로 Proxy로 감싸거나 AOP, @Autowired 검증, @PostConstruct 호출 시기 */ public class EnableEncryptablePropertiesBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
private static final Logger log = LoggerFactory.getLogger(EnableEncryptablePropertiesBeanFactoryPostProcessor.class);
private final ConfigurableEnvironment environment;
private final EncryptablePropertySourceConverter converter;public EnableEncryptablePropertiesBeanFactoryPostProcessor(ConfigurableEnvironment environment, EncryptablePropertySourceConverter converter) {
this.environment = environment;
this.converter = converter;
}public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
log.info(“Post-processing PropertySource instances”);
MutablePropertySources propSources = this.environment.getPropertySources();
//이 부분에서 래핑을 시도한다. this.converter.convertPropertySources(propSources);
}public int getOrder() {
return 2147483547;
}
} ``` - 실제 래핑하는
convertPropertySource를 살펴보면 ```java package com.ulisesbocchio.jasyptspringboot;
import com.ulisesbocchio.jasyptspringboot.aop.EncryptableMutablePropertySourcesInterceptor;
import com.ulisesbocchio.jasyptspringboot.aop.EncryptablePropertySourceMethodInterceptor;
import com.ulisesbocchio.jasyptspringboot.configuration.EnvCopy;
import com.ulisesbocchio.jasyptspringboot.wrapper.EncryptableEnumerablePropertySourceWrapper;
import com.ulisesbocchio.jasyptspringboot.wrapper.EncryptableMapPropertySourceWrapper;
import com.ulisesbocchio.jasyptspringboot.wrapper.EncryptableMutablePropertySourcesWrapper;
import com.ulisesbocchio.jasyptspringboot.wrapper.EncryptablePropertySourceWrapper;
import com.ulisesbocchio.jasyptspringboot.wrapper.EncryptableSystemEnvironmentPropertySourceWrapper;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.env.CommandLinePropertySource;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.core.env.SystemEnvironmentPropertySource;
public class EncryptablePropertySourceConverter {
…
public void convertPropertySources(MutablePropertySources propSources) {
((List)propSources.stream()
.filter((ps) -> !(ps instanceof EncryptablePropertySource)) //Encryptable이 아닌 것만
.map(this::makeEncryptable) //Encryptable로 바꾸고
.collect(Collectors.toList()))
.forEach((ps) -> propSources.replace(ps.getName(), ps)); //원래 이름으로 교체
} } ``` - 위와 같이 EncryptableMapPropertySourceWrapper로 래핑이 되어 있으면 넘어가고 아니면 래핑한다.
package com.ulisesbocchio.jasyptspringboot.wrapper;
import com.ulisesbocchio.jasyptspringboot.EncryptablePropertyFilter;
import com.ulisesbocchio.jasyptspringboot.EncryptablePropertyResolver;
import com.ulisesbocchio.jasyptspringboot.EncryptablePropertySource;
import com.ulisesbocchio.jasyptspringboot.caching.CachingDelegateEncryptablePropertySource;
import java.util.Map;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
public class EncryptableMapPropertySourceWrapper extends MapPropertySource implements EncryptablePropertySource<Map<String, Object>> {
private final CachingDelegateEncryptablePropertySource<Map<String, Object>> encryptableDelegate;
public EncryptableMapPropertySourceWrapper(MapPropertySource delegate, EncryptablePropertyResolver resolver, EncryptablePropertyFilter filter) {
super(delegate.getName(), (Map)delegate.getSource());
this.encryptableDelegate = new CachingDelegateEncryptablePropertySource(delegate, resolver, filter);
}
public Object getProperty(String name) {
return this.encryptableDelegate.getProperty(name);
}
public PropertySource<Map<String, Object>> getDelegate() {
return this.encryptableDelegate;
}
}
- 위와 같이 래핑된 객체가 반환된다. 여기서
this.encryptableDeletage.getProperty(..)가 호출 될 때 비로소ENC()로 된 문제를 decrypt한다. -
- encryptableDelegate로 실제 처리를 위임하는 객체는 아래와 같다. ```java
public class DefaultPropertyResolver implements EncryptablePropertyResolver {
private final Environment environment;
private StringEncryptor encryptor;
private EncryptablePropertyDetector detector;
public DefaultPropertyResolver(StringEncryptor encryptor, Environment environment) {
this(encryptor, new DefaultPropertyDetector(), environment);
}
public DefaultPropertyResolver(StringEncryptor encryptor, EncryptablePropertyDetector detector, Environment environment) {
this.environment = environment;
Assert.notNull(encryptor, "String encryptor can't be null");
Assert.notNull(detector, "Encryptable Property detector can't be null");
this.encryptor = encryptor;
this.detector = detector;
}
public String resolvePropertyValue(String value) {
Optional var10000 = Optional.ofNullable(value);
Environment var10001 = this.environment;
Objects.requireNonNull(var10001);
var10000 = var10000.map(var10001::resolvePlaceholders);
EncryptablePropertyDetector var3 = this.detector;
Objects.requireNonNull(var3);
return (String)var10000.filter(var3::isEncrypted).map((resolvedValue) -> {
try {
String unwrappedProperty = this.detector.unwrapEncryptedValue(resolvedValue.trim());
String resolvedProperty = this.environment.resolvePlaceholders(unwrappedProperty);
return this.encryptor.decrypt(resolvedProperty);
} catch (EncryptionOperationNotPossibleException e) {
throw new DecryptionException("Unable to decrypt property: " + value + " resolved to: " + resolvedValue + ". Decryption of Properties failed, make sure encryption/decryption passwords match", e);
}
}).orElse(value);
} } ``` - 결과적으로 EncryptablePropertyDetector로 `ENC()` 를 감지하고 필요하면 복호화하고 아니라면 그냥 내보낸다. - 아래는 추가로 Detector에 대한 구현이 사항이다. ```java package com.ulisesbocchio.jasyptspringboot.detector;
import com.ulisesbocchio.jasyptspringboot.EncryptablePropertyDetector;
import org.springframework.util.Assert;
public class DefaultPropertyDetector implements EncryptablePropertyDetector {
private String prefix = “ENC(“;
private String suffix = “)”;
public DefaultPropertyDetector() {
}
public DefaultPropertyDetector(String prefix, String suffix) {
Assert.notNull(prefix, "Prefix can't be null");
Assert.notNull(suffix, "Suffix can't be null");
this.prefix = prefix;
this.suffix = suffix;
}
public boolean isEncrypted(String property) {
if (property == null) {
return false;
} else {
String trimmedValue = property.trim();
return trimmedValue.startsWith(this.prefix) && trimmedValue.endsWith(this.suffix);
}
}
public String unwrapEncryptedValue(String property) {
return property.substring(this.prefix.length(), property.length() - this.suffix.length());
} } ```