前言

在现代软件开发中,保护敏感信息是至关重要的。随着数据泄露和安全漏洞的频繁发生,加密配置数据变得越来越重要。Spring Boot作为轻量级的Java应用开发框架,提供了一种简单而有效的方式来集成Jasypt,实现配置数据的加解密处理。Jasypt是一个灵活的加密库,支持多种加密算法,非常适合与Spring Boot配合使用。

然而,在集成Jasypt的过程中,我们可能会遇到配置错误或兼容性问题,导致应用无法正确加载或解析加密的配置。本文将分享一个典型的配置错误及其彻底的解决办法,帮助您更好地理解和使用Jasypt进行配置加密。

问题

使用了 springboot 集成 jasypt 版本使用的最新版本 3.0.5
其他的环境如下
spring cloud 2021.0.5
spring cloud alibaba 2021.0.5.0
spring boot 2.6.13

并且使用到jasypt 的自定义加解密 具体的报错信息如下

java.lang.IllegalStateException: String Encryptor custom Bean not found with name 'sm4Encryptor'
	at com.ulisesbocchio.jasyptspringboot.encryptor.DefaultLazyEncryptor.lambda$new$2(DefaultLazyEncryptor.java:44)
	at java.base/java.util.Optional.orElseGet(Optional.java:364)
	at com.ulisesbocchio.jasyptspringboot.encryptor.DefaultLazyEncryptor.lambda$new$3(DefaultLazyEncryptor.java:42)
	at com.ulisesbocchio.jasyptspringboot.util.Singleton.lambda$new$6(Singleton.java:97)
	at com.ulisesbocchio.jasyptspringboot.util.Singleton.get(Singleton.java:109)
	at com.ulisesbocchio.jasyptspringboot.encryptor.DefaultLazyEncryptor.decrypt(DefaultLazyEncryptor.java:73)
	at com.ulisesbocchio.jasyptspringboot.resolver.DefaultPropertyResolver.lambda$resolvePropertyValue$0(DefaultPropertyResolver.java:61)
	at java.base/java.util.Optional.map(Optional.java:260)
	at com.ulisesbocchio.jasyptspringboot.resolver.DefaultPropertyResolver.resolvePropertyValue(DefaultPropertyResolver.java:57)
	at com.ulisesbocchio.jasyptspringboot.resolver.DefaultLazyPropertyResolver.resolvePropertyValue(DefaultLazyPropertyResolver.java:69)
	at com.ulisesbocchio.jasyptspringboot.caching.CachingDelegateEncryptablePropertySource.getProperty(CachingDelegateEncryptablePropertySource.java:74)
	at com.ulisesbocchio.jasyptspringboot.wrapper.EncryptableMapPropertySourceWrapper.getProperty(EncryptableMapPropertySourceWrapper.java:40)
	at org.springframework.core.env.PropertySourcesPropertyResolver.getProperty(PropertySourcesPropertyResolver.java:85)
	at org.springframework.core.env.PropertySourcesPropertyResolver.getPropertyAsRawString(PropertySourcesPropertyResolver.java:74)
	at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:153)
	at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126)
	at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:239)
	at org.springframework.core.env.AbstractPropertyResolver.resolvePlaceholders(AbstractPropertyResolver.java:202)
	at org.springframework.core.env.AbstractEnvironment.resolvePlaceholders(AbstractEnvironment.java:625)
	at java.base/java.util.Optional.map(Optional.java:260)
	at com.ulisesbocchio.jasyptspringboot.resolver.DefaultPropertyResolver.resolvePropertyValue(DefaultPropertyResolver.java:55)
	at com.ulisesbocchio.jasyptspringboot.resolver.DefaultLazyPropertyResolver.resolvePropertyValue(DefaultLazyPropertyResolver.java:69)
	at com.ulisesbocchio.jasyptspringboot.caching.CachingDelegateEncryptablePropertySource.getProperty(CachingDelegateEncryptablePropertySource.java:74)
	at com.ulisesbocchio.jasyptspringboot.wrapper.EncryptableMapPropertySourceWrapper.getProperty(EncryptableMapPropertySourceWrapper.java:40)
	at org.springframework.cloud.bootstrap.encrypt.AbstractEnvironmentDecrypt.merge(AbstractEnvironmentDecrypt.java:103)
	at org.springframework.cloud.bootstrap.encrypt.AbstractEnvironmentDecrypt.merge(AbstractEnvironmentDecrypt.java:81)
	at org.springframework.cloud.bootstrap.encrypt.AbstractEnvironmentDecrypt.decrypt(AbstractEnvironmentDecrypt.java:69)
	at org.springframework.cloud.bootstrap.encrypt.EnvironmentDecryptApplicationInitializer.initialize(EnvironmentDecryptApplicationInitializer.java:95)
	at org.springframework.cloud.bootstrap.BootstrapApplicationListener$DelegatingEnvironmentDecryptApplicationInitializer.initialize(BootstrapApplicationListener.java:409)

问题排查

从报错信息来看 提示String Encryptor 自定义的bean没有 bean是新定义的 bean的名称也确认无误 使用配置方法如下

/**
 * @author leon
 * @date 2023-08-19 15:58:42
 */
@Slf4j
@Configuration
public class JasyptConfig {

    @Bean("sm4Encryptor")
    public StringEncryptor stringEncryptor() {
        log.info("JasyptConfig 初始化:{}", "sm4Encryptor");
        return new Sm4Encryptor();
    }
}    

bean名称无误 ,注册方式没问题。 那就是需要获取bean的时间段内 bean还没有初始化,导致这个时间段无法获取成功,那解决的办法就是让这个bean提前加载

三。解决

(1). 去除之前的自定义配置JasyptConfig
(2). 首先实现自定义的加密工具 需要实现 StringEncryptor 即可 我们这是用的国密4的对称加密算法 同时使用了 hutool的工具类 (已经完成)

import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.SM4;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.jasypt.encryption.StringEncryptor;

import java.nio.charset.StandardCharsets;

/**
 * @author leon
 * @date 2023-08-19 10:54:32
 */
@Slf4j
public class Sm4Encryptor implements StringEncryptor {

    private static final SM4 SM_4;


    static {
        byte[] key = "1234567891234560".getBytes();
        byte[] iv = "1234567891234560".getBytes();
        SM_4 = new SM4(Mode.CBC, Padding.PKCS5Padding, key, iv);
    }

    @Override
    public String encrypt(String message) {
      	return SM_4.encryptHex(message);
    }

    @Override
    public String decrypt(String encryptedMessage) {
      	return SM_4.decryptStr(encryptedMessage);
    }

    public static void main(String[] args) {
       
        String originalValue = "123456";
        String encrypted = SM_4.encryptHex(originalValue);
        String decrypted = SM_4.decryptStr(encrypted);
        log.info("Original Value: {}" , originalValue);
        log.info("Encrypted Value: {}" , encrypted);
        log.info("Decrypted Value: {}", decrypted);
    }


}

(3). 注册该bean 这需要使用特殊的方式注册bean,我们希望在 Spring Boot 应用程序启动期间提前将自定义的加解密类添加到 Spring 的应用程序上下文中,以便在配置文件加载之前,加解密类已经可用。这样可以确保配置文件中的加密属性能够在加载时使用正确的自定义加解密逻辑进行解密

新建初始化类

import com.stlye.encryptor.Sm4Encryptor;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * @date 2023-08-21 13:57:20
 * @author leon
 */
public class CustomContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        applicationContext.getBeanFactory().registerSingleton("sm4Encryptor", new Sm4Encryptor());
    }
}

(4). 其次在resource 下新建 META_INF文件夹 并新建 spring.factories 文件

org.springframework.context.ApplicationContextInitializer=com.style.order.config.CustomContextInitializer

重点是这里 因为我们希望在 Spring Boot 应用程序启动期间提前将自定义的加解密类添加到 Spring 的应用程序上下文中,以便在配置文件加载之前,加解密类已经可用。这样可以确保配置文件中的加密属性能够在加载时使用正确的自定义加解密逻辑进行解密 ,所以我们需要如上方式提前注册bean。

验证与测试

重新启动应用后,检查是否还有之前的String Encryptor custom Bean not found with name ‘xxxEncryptor‘ 错误。如果没有,说明自定义的加密器已经成功加载并生效。

总结

通过实现ApplicationContextInitializer接口并提前注册自定义的加密器Bean,我们可以确保在Spring Boot应用启动期间尽早加载和注册自定义的加密器。这样,配置文件中的加密属性就能在加载时使用正确的自定义加解密逻辑进行解密。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐