背景

在使用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. 不同的项目加密同样的内容后的密文,不能被同样的密码解密出来

jasypt在springboot项目中遇到异常:Error creating bean with name 'enableEncryptablePropertySourcesPostProcessor' defined in class path resource的更多相关文章

  1. SpringBoot启动zipkin-server报错Error creating bean with name ‘armeriaServer’ defined in class path resource

    目前,GitHub 上最新 release 版本是 Zipkin 2.12.9,从 2.12.6 版本开始有个较大的更新,迁移使用 Armeria HTTP 引擎. 从此版本开始,若直接添加依赖的 S ...

  2. Error creating bean with name 'enableRedisKeyspaceNotificationsInitializer' defined in class path resource

    我们在SpringBoot中用Jedis来访问Redis,其中Redis是采用集群(单机没有本篇文章的问题)的方式,在启用Redis的时候碰到如上问题. 错误的核心信息如下: Error creati ...

  3. 【报错】org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'webSocketHandlerMapping' defined in class path resource

    环境:maven+eclipse+jdk1.8 [tomcat使用的是maven自带的插件,实质原因就是出在tomcat版本问题] 背景:在进行SSM集成WebSocket的时候,项目启动报org.s ...

  4. Error creating bean with name 'us' defined in class path resource [com/liuyang/test/DI/beans2.xml]: Cannot resolve reference to bean 'daoa' while setting bean property 'daoa'; nested exception is org.

    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'us' defined ...

  5. 使用SpringMVC报错 Error creating bean with name 'conversionService' defined in class path resource [springmvc.xml]

    使用SpringMVC报错 Error creating bean with name 'conversionService' defined in class path resource [spri ...

  6. 报错org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in class path resource [bean.xml]

    报这种错的原因基本上是applicationContext.xml文件中bean配置错误,错误如下: org.springframework.beans.factory.BeanCreationExc ...

  7. org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory' defined in class path resource

    二月 20, 2017 3:09:47 下午 org.apache.catalina.startup.SetAllPropertiesRule begin警告: [SetAllPropertiesRu ...

  8. org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource

    spring boot web项目运行时提示如下错误 org.springframework.beans.factory.BeanCreationException: Error creating b ...

  9. Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]: Invocation of init method fail

    SpringBoot 单元测试报错 @RunWith(SpringRunner.class) @SpringBootTest public class ProductCategoryRepositor ...

随机推荐

  1. [UNP] TCP 多进程服务器

    UNP Part-2: Chapter 5. TCP Client/Server Example 的读书笔记. 阅读本文前,建议先阅读多线程服务器的实现,熟悉常见的 TCP 网络通信 API 的基本使 ...

  2. HDOJ-1213(简单并查集)

    How many tables HDOJ-1213 #include<iostream> #include<cstring> #include<cstdio> #i ...

  3. ClickHouse元数据异常-MySQLHandlerFactory:Failed to read RSA key pair from server

    Clickhouse版本:20.3.6.40-2 clickhouse集群三个节点,一分片,三副本,三个节点数据完全一样 1. 问题描述 在使用连接工具操作时,发现其中一个节点连接拒绝,无法操作,另外 ...

  4. 鸿蒙OS前端开发入门指南:网络图片_Image渲染网络图片

    目录: 1.开启明文传输 2.权限申请 3.引入http插件 4.案例展示 5.<鸿蒙OS前端开发入门指南>文章合集 开启明文传输 在config.json配置文件添加如下配置(如果不开启 ...

  5. 任务3 PHP配置 1. PHP基础配置

    查看PHP配置文件得位置 #/ucsr/local/php/bin/php -i |grep -i "loaded configuration file" # cp /usr/lo ...

  6. 安卓Media相关类测试demo

    最近在研究安卓系统给app开发者提供的标准Media相关的工具类,本人做了一些demo来测试这些工具的使用方法. 本demo包含若干apk源码,需要说明以下几点: 1. 构建方式 Makefile使用 ...

  7. 【图像处理】OpenCV+Python图像处理入门教程(五)阈值处理

    这篇随笔介绍使用OpenCV进行图像处理的第五章 阈值处理. 5  阈值处理 阈值是指像素到达某临界值.阈值处理表示像素到达某临界值后,对该像素点进行操作和处理. 例如:设定一幅图像素阈值为200,则 ...

  8. 2018ICPC南京K. Kangaroo Puzzle

    题目:在一个20×20的地图上,1表示有袋鼠,0表示有障碍物,边界外和障碍物上不能走.        要求给出一个50000步以内的操作,每一步操作为'L', 'R', 'U', 'D', 表示所有袋 ...

  9. AggregateReport V2.2.0

    1. 说明 AggregateReport V2.2.0终于跟大家见面了!从V1.0.0到V2.2.0 宝路听取了很多同学的建议!感谢大家一路的信任与支持! 2. 文章导读 如果不了解此工具的同学,建 ...

  10. Java例题_17 猴子偷桃问题

    1 /*17 [程序 17 猴子吃桃问题] 2 题目:猴子吃桃问题 3 猴子第一天摘下若干个桃子,当即吃了一半,还不瘾,又多吃了一个 4 第二天早上又将剩下的桃子吃掉一半,又多吃了一个. 5 以后每天 ...