background

While encrypting sensitive information in Spring Boot’s configuration file with Jasypt, an exception was encountered when booting directly with Stater

<dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> The < version > 3.0.3 < / version > < / dependency >

The following exception was encountered:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'enableEncryptablePropertySourcesPostProcessor' defined in class path resource [com/ulisesbocchio/jasyptspringboot/configuration/EnableEncryptablePropertiesConfiguration.class]: Unsatisfied dependency expressed through method 'enableEncryptablePropertySourcesPostProcessor' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'xxxDao' defined in xxxDao defined in @EnableJpaRepositories declared on Application: Unsatisfied dependency expressed through constructor parameter 1: Ambiguous argument values for parameter of type [javax.persistence.EntityManager] - did you specify the correct bean references as arguments? at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:797) at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:538 ) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutow ireCapableBeanFactory.java:1336) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableB eanFactory.java:1176) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFac tory.java:556) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFacto ry.java:516) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:23 4) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207) at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegis trationDelegate.java:172) at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContex t.java:707) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:533) at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationConte xt.java:143) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) ...

The above information is refers to the enableEncryptablePropertySourcesPostProcessor created, due to failed to create other type of Bean failure.

To say about this problem because the spring spring BeanFactoryPostProcessor boot and the custom custom @ EnableJpaRepositories repositoryFactoryBeanClass caused by incompatible when start to create, @ Repository annotated beans in advance is initialized (create enableEncryptablePropertySourcesPostProcessor, because spring boot mechanism led to some of the class is instantiated in advance, But the BeanFactoryPostProcessor that handles @Repository has not yet been loaded in.)

If it is not the custom in @ EnableJpaRepositories custom repositoryFactoryBeanClass, above will not appear abnormal

The solution

After that, I tried to implement Jasypt by myself, but it still needed the BeanFactoryPostProcessor implementation of Jasypt, so I gave up. Finally, I used the rewrite PropertySource method. Add reflection to do its own thing to decrypt the encrypted information that has been read (before putting the bean in the container, i.e. before annotations like @value take effect)

1. Add the following package to remove Jasypt’s Spring Boot Stater package

<dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot</artifactId> The < version > 3.0.3 < / version > < / dependency >

2. Define @Configuration to inject beans into PropertySource

//JasyptPropertyValueConfig.java import org.springframework.beans.factory.config.PropertyOverrideConfigurer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class JasyptPropertyValueConfig { @Bean public PropertyOverrideConfigurer jasyptPropertyOverrideConfigurer() { return new JasyptPropertyValueHandler(); }}
//JasyptPropertyValueHandler.java import org.jasypt.util.text.BasicTextEncryptor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.PropertyOverrideConfigurer; import org.springframework.boot.env.OriginTrackedMapPropertySource; import org.springframework.boot.origin.OriginTrackedValue; import org.springframework.context.EnvironmentAware; import org.springframework.core.env.Environment; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; import org.springframework.core.env.SimpleCommandLinePropertySource; import org.springframework.web.context.support.StandardServletEnvironment; import java.lang.reflect.Field; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.stream.StreamSupport; public class JasyptPropertyValueHandler extends PropertyOverrideConfigurer implements EnvironmentAware { private static BasicTextEncryptor textEncryptor = null; private final String KEY_SEED = "jasypt.encryptor.password"; private final String PREFIX = "ENC("; private final String SUFFIX = ")"; private final byte[] tmp_lock = new byte[1]; private boolean isInit; private String seed; private Environment environment; public JasyptPropertyValueHandler() { } @Override public void setEnvironment(Environment environment) { this.environment  = environment; } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { MutablePropertySources propertySources = ((StandardServletEnvironment) environment).getPropertySources(); convertPropertySources(propertySources); super.postProcessBeanFactory(beanFactory); } public void convertPropertySources(MutablePropertySources propSources) { initSeed(); / / command line arguments SimpleCommandLinePropertySource / / yml OriginTrackedMapPropertySource configuration file parameters StreamSupport.stream(propSources.spliterator(), false) .filter(ps -> (ps instanceof OriginTrackedMapPropertySource) || (ps instanceof SimpleCommandLinePropertySource)) .forEach(ps -> { if (ps instanceof OriginTrackedMapPropertySource) { handleConfigFile(ps); } else if (ps instanceof SimpleCommandLinePropertySource) { handleCommandLine(ps); } propSources.replace(ps.getName(), ps); }); } // Processes the default configuration file for Spring Boot, Private void HandleConfigFile (propertySource ps) {Map<String, OriginTrackedValue> result = (Map<String, OriginTrackedValue>) ps.getSource(); for (String key : result.keySet()) { OriginTrackedValue value = result.get(key); if (checkNeedProcessOverride(key, String.valueOf(value.getValue()))) { System.out.println(value); String decryptedValue = decryptValue(seed, String.valueOf(value.getValue())); try { Field valueField = OriginTrackedValue.class.getDeclaredField("value"); valueField.setAccessible(true); valueField.set(value, decryptedValue); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); }}}} // Procures the argument to replace spring boot on the command line, For example - spring. The datasource. The parameters of the password form private void handleCommandLine PropertySource (ps) {try {Object commandLineArgs = ps.getSource(); Field valueField = commandLineArgs.getClass().getDeclaredField("optionArgs"); valueField.setAccessible(true); boolean hasEncrypt = false; Map<String, List<String>> result = (Map<String, List<String>>) valueField.get(commandLineArgs); for (String key : result.keySet()) { List<String> values = result.get(key); if (values.size() == 1) { if (checkNeedProcessOverride(key, String.valueOf(values.get(0)))) { hasEncrypt = true; String decryptedValue = decryptValue(seed, String.valueOf(values.get(0))); values.clear(); values.add(decryptedValue); } } } if (hasEncrypt) { valueField.set(commandLineArgs, result); } } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } private boolean checkNeedProcessOverride(String key, String value) { if (KEY_SEED.equals(key)) { return false; } return StringUtils.isNotBlank(value) && value.startsWith(PREFIX) && value.endsWith(SUFFIX); } private void initSeed() { if (! this.isInit) { this.isInit = true; this.seed = this.environment.getProperty(KEY_SEED); if (StringUtils.isNotBlank(this.seed)) { return; } try { Properties properties = mergeProperties(); / / start from the command line, get - Djasypt. The encryptor. The value of the password this. Seed = properties. The getProperty (KEY_SEED); } catch (Exception E) {System.out.println(" No encryption key configured "); } } } private String decryptValue(String seed, String value) { value = value.replace(PREFIX, "").replace(SUFFIX, ""); value = getEncryptor(seed).decrypt(value); return value; } private BasicTextEncryptor getEncryptor(String seed) { if (textEncryptor == null) { synchronized (tmp_lock) { if (textEncryptor == null) { textEncryptor = new BasicTextEncryptor(); textEncryptor.setPassword(seed); } } } return textEncryptor; }}

3. Because the encrypted content generated by Jasypt is different each time and depends on the project, we wrote a Controller class to encrypt the content

//JasyptController.java import org.jasypt.util.text.BasicTextEncryptor; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("jasypt") public class JasyptController { @Value(${jasypt.encryptor.password}) private String seed; private static BasicTextEncryptor textEncryptor = null; private final byte[] tmp_lock = new byte[1]; @RequestMapping("encrypt") public String encrypt(String value){ textEncryptor=getEncryptor(seed); return textEncryptor.encrypt(value); } @RequestMapping("decrypt") public String decrypt(String value){ textEncryptor=getEncryptor(seed); return textEncryptor.decrypt(value); } private BasicTextEncryptor getEncryptor(String seed) { if (textEncryptor == null) { synchronized (tmp_lock) { if (textEncryptor == null) { textEncryptor = new BasicTextEncryptor(); textEncryptor.setPassword(seed); } } } return textEncryptor; }}
  • After the project is started, request the interface/jasypt/encrypt? Value = Need to encrypt content, you can get the ciphertext

How to use

With that done, you can use the Jasypt configuration file and command-line approach

1. In the configuration file

Let me write an application.properties here

Spring. The datasource. Password = ENC (encrypted cryptograph)

The command line then starts the JAR package with the password

Java-djasypt.encrypt. password= Encrypt password -jar XXX. Jar

2. The command line

Java - Djasypt. Encrypt. Password = encrypted password - jar -- spring. The datasource. The password = ENC (encrypted cryptograph) XXX. The jar

This follows the override priority of Spring Boot.

conclusion

Since this is not an implementation of Jasypt and only emulates the usual decryption of configuration files and commands by default, the Jasypt customizations are not available, so you can implement them yourself

Tips: 1. The same content will be encrypted differently each time. 2. The ciphertext encrypted by different items with the same content cannot be decrypted with the same password