2021年4月25日星期日

jasypt在springboot项目中遇到异常:Error creating bean with name 'enableEncryptablePropertySourcesPostProcessor' defined in class path resource

背景

在使用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.javaimport org.springframework.beans.factory.config.PropertyOverrideConfigurer;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class JasyptPropertyValueConfig { @Bean public PropertyOverrideConfigurer jasyptPropertyOverrideConfigurer() {  return new JasyptPropertyValueHandler(); }}
//JasyptPropertyValueHandler.javaimport 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.javaimport 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. 不同的项目加密同样的内容后的密文,不能被同样的密码解密出来








原文转载:http://www.shaoqun.com/a/707734.html

跨境电商:https://www.ikjzd.com/

急速:https://www.ikjzd.com/w/1861

一淘比价网:https://www.ikjzd.com/w/1698


背景在使用jasypt对springboot的配置文件中的敏感信息进行加密处理时,使用stater直接启动时,遇到了一个异常<dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId>&l
stylenanda官网:https://www.ikjzd.com/w/1675.html
usps国际快递查询:https://www.ikjzd.com/w/513
首信易支付:https://www.ikjzd.com/w/1841
比"黑科技"更有用的Listing优化技巧,亚马逊卖家请收藏!:https://www.ikjzd.com/home/12265
Amazon Business按发票付款政策于8月8日正式生效!:https://www.ikjzd.com/home/4593
口述:老公的旧爱送我情趣内衣:http://lady.shaoqun.com/a/272066.html

没有评论:

发表评论