background
When using jasypt to encrypt sensitive information in the spring boot configuration file, an exception was encountered when using statuser to start directly
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
Encountered the following exception:
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(AbstractAutowireCapableBeanFactory.java:1336)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1176)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:556)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.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:234)
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(PostProcessorRegistrationDelegate.java:172)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:707)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:533)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.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 refers to the enableEncryptablePropertySourcesPostProcessor
failure due to the failure to create other types of Bean when it was created.
Let me talk about this problem first because the spring boot BeanFactoryPostProcessor and the custom repositoryFactoryBeanClass in the custom @EnableJpaRepositories are not compatible when they are created at startup. The @Repository annotated bean is initialized in advance (when the enableEncryptablePropertySourcesPostProcessor is created, because of the spring boot mechanism As a result, some classes are instantiated in advance, but the BeanFactoryPostProcessor processing @Repository has not been loaded)
If you do not customize the custom repositoryFactoryBeanClass in @EnableJpaRepositories, the above exception will not occur
Solution
After that, I wanted to implement the jasypt method by myself, but the BeanFactoryPostProcessor implementation method that still needs jasypt appeared, so I gave up, and finally used the rewriting PropertySource method, plus reflection to complete itself to decrypt the encrypted information that has been read (in the Before the bean is placed in the container, that is, before the annotations such as @Value take effect)
1. Introduce the following packages, remove the spring boot stater package of jasypt
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot</artifactId>
<version>3.0.3</version>
</dependency>
2. Define @Configuration to inject PropertySource beans
//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 parameter SimpleCommandLinePropertySource
// yml profile parametersOriginTrackedMapPropertySource
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);
});
}
//Handle spring boot's default configuration file, such as application.yml or application.properties to load all the content
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();
}
}
}
}
//Handle the replacement of spring boot arguments on the command line, for example in the form of the --spring.datasource.password argument
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();
//From the startup command line, get the value of -Djasypt.encryptor.password
this.seed = properties.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, it is also related to the project, so I wrote a controller class for content encryption
//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
/jasypt/encrypt?value=Need to encrypt content
, you can request the interface to get the ciphertext
how to use
After the above configuration is completed, you can use the configuration file and command line method of jasypt
1. In the configuration file
Write an application.properties here
spring.datasource.password=ENC(Encrypted ciphertext)
Then the command line uses the password to start the jar package
java -Djasypt.encrypt.password=Encrypted password -jar xxx.jar
2. Command line
java -Djasypt.encrypt.password=Encrypted password -jar --spring.datasource.password=ENC(Encrypted ciphertext) xxx.jar
The above follows the coverage priority of spring boot.
to sum up
Because this is not an implementation of jasypt, it just simulates the commonly used configuration files and command decryption methods by default, so the custom content of jasypt cannot be used, and those who are interested can implement it by themselves
tips:
1. jasypt the same content is encrypted with different ciphertext every time
2. The ciphertext after different items encrypt the same content cannot be decrypted by the same password
Read More:
- [Solved] Springboot Error: Error creating bean with name ‘dataSource‘ defined in class path resource
- SpringBoot integrates Es error: Error creating bean with name ‘restHighLevelClient‘ defined in class path resource
- [Solved] Error creating bean with name ‘configurationPropertiesBeans‘ defined in class path resource…
- [Solved] Error creating bean with name ‘redisConnectionFactory‘ defined in class path resource…
- [Solved] seata:Error creating bean with name ‘globalTransactionScanner‘ defined in class path resource
- [Solved] Error creating bean with name ‘entityManagerFactory‘ defined in class path resource
- [Solved] Error creating bean with name ‘mappingJackson2HttpMessageConverter‘ defined in class path resource
- [Solved] springCloud Startup Error: Error creating bean with name ‘defaultValidator‘ defined in class path resource
- Error creating bean with name ‘servletEndpointRegistrar‘ defined in class path resource
- [Solved] springboot Error: Error creating bean with name ‘configurationPropertiesBeans‘ defined in class path
- [Solved] Error creating bean with name ‘sqlSessionFactory‘ defined in class path resource
- [Solved] Error creating bean with name ‘configurationPropertiesBeans‘ defined in class path resource
- [Solved] RabbitMQ Error: Error creating bean with name ‘rabbitConnectionFactory‘ defined in class path resource
- [Solved] spring boot Startup Error: Error creating bean with name ‘requestMappingHandlerMapping‘ defined in class path
- Springboot integration shiro error: UnsatisfiedDependencyException: Error creating bean with name ‘getShiroFilterFact
- [Solved] Error creating bean with name ’emf’ defined in org.ngrinder.infra.config.DatabaseConf
- [Solved] Error creating bean with name ‘fileVisitController‘ defined in file
- Error creating bean with name ‘adminMapper‘ Injection of resource dependencies failed
- [Solved] Rabbitmq injection failed, bean creation failed, error creating bean with name ‘rabbitconnectionfactory’‘
- SpringBoot Integrating elasticsearch Error: Error creating bean with name ‘restHighLevelClient‘