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.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. 不同的項目加密同樣的內容后的密文,不能被同樣的密碼解密出來


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM