背景
在应用 jasypt 对 spring boot 的配置文件中的敏感信息进行加密解决时,应用 stater 间接启动时,遇到了一个异样
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
遇到如下异样:
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)
...
以上的信息是指 enableEncryptablePropertySourcesPostProcessor
创立时,因为创立其余类 Bean 失败而导致失败的。
先说一下这个问题是因为 spring boot 的 BeanFactoryPostProcessor 和自定义的 @EnableJpaRepositories 中的自定义 repositoryFactoryBeanClass 在启动创立时不兼容导致的,@Repository 注解的 bean 提前被初始化了(创立 enableEncryptablePropertySourcesPostProcessor 时,因为 spring boot 的机制导致了一些类提前被实例化了,然而解决 @Repository 的 BeanFactoryPostProcessor 还没有加载进来)
如果不自定义 @EnableJpaRepositories 中的自定义 repositoryFactoryBeanClass,就不会呈现以上异样
解决办法
之后就想本人实现一下 jasypt 的形式,不过呈现了还是须要 jasypt 的 BeanFactoryPostProcessor 实现形式,遂放弃,最初应用了重写 PropertySource 形式,加上反射实现本人来对曾经读取的加密信息进行解密(在把 bean 放到容器之前,也就是 @Value 等注解失效之前)
1. 引入如下包,去掉 jasypt 的 spring boot stater 包
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot</artifactId>
<version>3.0.3</version>
</dependency>
2. 定义 @Configuration 来注入 PropertySource 的 bean
//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();
// 命令行参数 SimpleCommandLinePropertySource
// yml 配置文件参数 OriginTrackedMapPropertySource
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);
});
}
// 解决 spring boot 的默认配置文件,例如 application.yml 或者 application.properties 中加载所有内容
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();
}
}
}
}
// 解决命令行中的替换 spring boot 的参数,例如 --spring.datasource.password 的参数模式
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();
// 从启动命令行中,获取 -Djasypt.encryptor.password 的值
this.seed = properties.getProperty(KEY_SEED);
} catch (Exception e) {System.out.println("未配置加密密钥");
}
}
}
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. 因为 jasypt 每次生成的加密后的内容不一样,还跟我的项目无关,所以写了一个 controller 类做内容加密
//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;
}
}
- 我的项目启动起来之后,申请接口
/jasypt/encrypt?value= 须要加密内容
,就能够失去密文了
如何应用
以上配置实现之后,就能够用 jasypt 那种配置文件和命令行的形式了
1. 配置文件中
这里写一个 application.properties
spring.datasource.password=ENC(加密后的密文)
而后命令行应用明码启动 jar 包
java -Djasypt.encrypt.password= 加密明码 -jar xxx.jar
2. 命令行
java -Djasypt.encrypt.password= 加密明码 -jar --spring.datasource.password=ENC(加密后的密文) xxx.jar
以上遵循 spring boot 的笼罩优先级。
总结
因为这个不是 jasypt 的实现,只是模仿了默认状况下罕用的配置文件和命令解密形式,所以 jasypt 的自定义内容并不能应用,有趣味的能够本人实现一遍
tips:
1. jasypt 同样的内容每次加密后的密文都不一样
2. 不同的我的项目加密同样的内容后的密文,不能被同样的明码解密进去