我试图在Spring中侦听应用程序事件并解密已加密(具有加密前缀)的应用程序属性。这个想法是创建一个Spring组件,如果属性值被加密,则在加载环境时将自动解密spring属性。
这需要与具有application.env.properties文件的旧版Spring应用程序一起使用(不带Spring Boot),以及具有yaml文件中定义的属性的最新Spring Boot应用程序。源,并且不应依赖于Spring Boot并可以与任何Spring版本一起使用。
public class DecryptingPropertiesListener
implements ApplicationListener<ContextRefreshedEvent>, Ordered {
public static final String PREFIX_KEY = "{decrypt}";
private String prefix;
private Encrypter encrypter = Encrypter.defaultInstance();
@Override
public void onApplicationEvent(ContextRefreshedEvent event ) {
Environment environment = event.getApplicationContext().getEnvironment();
prefix = environment.getProperty(PREFIX_KEY, "{encrypted}");
final MutablePropertySources propertySources = ((ConfigurableEnvironment) environment).getPropertySources();
Set<String> encryptedKeys = getKeysOfEncryptedPropertyValues(environment, propertySources);
addDecryptedValues(environment, propertySources, encryptedKeys);
}
private Set<String> getKeysOfEncryptedPropertyValues(Environment environment, MutablePropertySources propertySources) {
return streamFromIterator(propertySources.iterator())
.filter(EnumerablePropertySource.class::isInstance)
.map(EnumerablePropertySource.class::cast)
.flatMap(source -> asList(source.getPropertyNames()).stream())
.filter(this::isNotEncryptionConfigProperty)
.filter(key -> isEncrypted(environment.getProperty(key)))
.collect(toSet());
}
private boolean isNotEncryptionConfigProperty(String key) {
return !PREFIX_KEY.equals(key);
}
private Stream<PropertySource<?>> streamFromIterator(Iterator<PropertySource<?>> iterator) {
Iterable<PropertySource<?>> iterable = () -> iterator;
return StreamSupport.stream(iterable.spliterator(), false);
}
private void addDecryptedValues(Environment environment, MutablePropertySources propertySources, Set<String> encryptedKeys) {
Map<String, Object> decryptedProperties = encryptedKeys.stream()
.collect(toMap(
key -> key,
key -> decryptPropertyValue(environment.getProperty(key))));
propertySources.addFirst(new MapPropertySource("decryptedValues", decryptedProperties));
}
private String decryptPropertyValue(String encryptedPropertyValue) {
try {
return encrypter.decryptIfEncrypted(encryptedPropertyValue);
}
catch (EncryptionException e) {
throw new RuntimeException("Unable to decrypt property value '" + encryptedPropertyValue + "'", e);
}
}
private boolean isEncrypted(Object propertyValue) {
return propertyValue != null && propertyValue instanceof String && ((String)propertyValue).startsWith(prefix);
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
但是问题是,我没有看到此行返回的应用程序属性,((ConfigurableEnvironment)环境).getPropertySources(); 我可以在这里看到系统属性,但不能看到应用程序。知道如何在这里加载应用程序属性并对其解密吗? 谢谢
编辑:添加样本属性文件。想法是将此通用jar添加为各种Web应用程序的Maven依赖项,包括旧版和新版Spring Boot。 以下属性文件的格式为myapp.env.properties,并且myapp.system.properties具有定义的env。但是spring boot应用程序使用.yaml文件。服务和安全密码必须具有前缀,因此需要解密。
base.url=http://localhost:8080/myapp
service.password={decrypt}123456789==
security.password={decrypt}abcdefgh==
help.email.address=support@gmail.com
答案 0 :(得分:1)
您可以使用EncryptablePropertyPlaceholderConfigurer
并提供StringEncryptor
的实例,它将为您处理属性解密。如果需要,还可以扩展该类以加载其他属性。
示例:
@Configuration
public class PropertyConfiguration {
@Bean(name="envPropertyConfigurer")
public EncryptablePropertyPlaceholderConfigurer getConfigurer() {
return new EncryptablePropertyPlaceholderConfigurer (encryptor());
}
private StringEncryptor encryptor() {
StandardPBEStringEncryptor s_encryptor = new StandardPBEStringEncryptor();
s_encryptor.setAlgorithm("PBEWithMD5AndDES");
s_encryptor.setPassword("secretKey");
return s_encryptor;
}
}
您还可以像这样在spring xml配置中执行此操作
<bean id="envPropertyEncryptor" class="org.jasypt.encryption.pbe.StandardPBEStringEncryptor">
<property name="algorithm" value="PBEWithMD5AndDES" />
<property name="algorithm" value="secretKey" />
</bean>
<bean id="envPropertyConfigurer" class="org.jasypt.spring.properties.EncryptablePropertyPlaceholderConfigurer">
<constructor-arg ref="envPropertyEncryptor" />
</bean>
您也可以从文件中读取密钥,而不是在此处直接设置密钥。您可以通过使用config
设置StandardPBEStringEncryptor FileStringPBEConfig
属性来做到这一点。
答案 1 :(得分:0)
我结束了实现自定义ApplicationContextInitializer的过程,并覆盖了initialize()以从环境中解密属性并以高优先级插入它们,以便它们覆盖加密的值。
public class PropertyContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize( ConfigurableApplicationContext applicationContext ) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
for ( PropertySource<?> propertySource : environment.getPropertySources() ) {
Map<String, Object> propertyOverrides = new LinkedHashMap<>();
// call to decrypt method
decryptProperty( propertySource, propertyOverrides );
if ( !propertyOverrides.isEmpty() ) {
PropertySource<?> decodedProperties = new MapPropertySource( "decrypt " + propertySource.getName(),
propertyOverrides );
environment.getPropertySources().addBefore( propertySource.getName(), decodedProperties );
}
}
}