背景

在应用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. 不同的我的项目加密同样的内容后的密文,不能被同样的明码解密进去