Spring Boot集成Jasypt踩坑实录:自定义SM4加密器Bean加载失败的终极解决方案
Spring Boot集成Jasypt自定义加密器从Bean加载失败到源码级解决方案如果你在Spring Boot项目中尝试集成Jasypt来实现配置加密特别是需要自定义加密算法比如国密SM4时很可能遇到过那个令人头疼的错误String Encryptor custom Bean not found with name xxxEncryptor。这个错误看似简单背后却隐藏着Spring Boot启动流程中Bean加载顺序的复杂机制。今天我就结合自己踩过的坑深入分析这个问题的根源并分享几种真正有效的解决方案。很多开发者第一次遇到这个问题时第一反应是检查Bean定义是否正确——确实Bean名称、配置类注解、依赖注入看起来都没问题。但重启应用后同样的错误依然出现。这时候你可能会开始怀疑人生明明Bean已经定义好了为什么Spring就是找不到它1. 问题根源Spring Boot启动流程中的时序陷阱要理解这个问题我们需要先深入Spring Boot的启动机制。Jasypt在Spring Boot中的集成是通过自动配置完成的而自动配置的加载时机与我们的自定义Bean定义存在一个关键的时间差。1.1 Jasypt的自动配置机制Jasypt-spring-boot-starter通过JasyptSpringBootAutoConfiguration类提供自动配置。这个配置类会注册一个DefaultLazyEncryptor实例这个实例负责在运行时查找并加载我们定义的自定义加密器。关键代码逻辑如下Configuration public class EncryptablePropertyResolverConfiguration { Bean(name lazyJasyptStringEncryptor) public StringEncryptor stringEncryptor( final EnvCopy envCopy, final BeanFactory bf) { final String customEncryptorBeanName envCopy.get() .resolveRequiredPlaceholders(${jasypt.encryptor.bean:jasyptStringEncryptor}); final boolean isCustom envCopy.get() .containsProperty(jasypt.encryptor.bean); return new DefaultLazyEncryptor(envCopy.get(), customEncryptorBeanName, isCustom, bf); } }这里的关键在于DefaultLazyEncryptor的构造函数逻辑public DefaultLazyEncryptor(final ConfigurableEnvironment e, final String customEncryptorBeanName, boolean isCustom, final BeanFactory bf) { singleton new Singleton(() - Optional.of(customEncryptorBeanName) .filter(bf::containsBean) .map(name - (StringEncryptor) bf.getBean(name)) .orElseGet(() - { if (isCustom) { throw new IllegalStateException( String.format(String Encryptor custom Bean not found with name %s, customEncryptorBeanName)); } return createDefault(e); })); }注意这里的isCustom参数非常重要。当我们在配置文件中设置了jasypt.encryptor.bean属性时isCustom会被设置为true。此时如果BeanFactory中找不到对应名称的Bean就会抛出我们遇到的错误。1.2 Bean加载顺序的时间线理解Spring Boot启动过程中各个阶段的发生顺序是解决这个问题的关键阶段主要操作与Jasypt相关的影响1. 环境准备加载application.yml/properties解析占位符Jasypt需要解密配置但此时自定义Bean还未注册2. Bean定义加载扫描Configuration类注册Bean定义我们的自定义加密器Bean定义在此阶段被注册3. Bean实例化实例化所有单例Bean自定义加密器Bean在此阶段被创建4. 属性解析解析Value注解和配置属性需要加密器但可能Bean还未准备好问题就出在第1阶段和第4阶段。当Spring Boot在启动早期解析配置文件时特别是使用ConfigurationProperties绑定的属性Jasypt的DefaultLazyEncryptor已经被调用去解密配置值。如果此时我们的自定义加密器Bean还没有被注册到BeanFactory中就会触发Bean not found异常。1.3 复现问题的典型配置让我们看看一个典型的错误配置是什么样的Configuration public class JasyptConfig { Bean(sm4Encryptor) public StringEncryptor stringEncryptor() { // 使用国密SM4算法 return new Sm4Encryptor(); } }# application.yml jasypt: encryptor: bean: sm4Encryptor # 指定使用自定义的Bean spring: datasource: password: ENC(加密后的密码字符串) url: jdbc:mysql://localhost:3306/test username: root这个配置看起来完全正确但为什么还是会失败呢原因在于Spring的Configuration类虽然被扫描到了但Bean的实际注册和初始化时机可能晚于某些配置属性的解析时机。2. 解决方案一使用ApplicationContextInitializer提前注册最直接有效的解决方案是利用Spring的ApplicationContextInitializer机制在应用上下文初始化的最早阶段注册我们的自定义加密器。2.1 实现自定义的ApplicationContextInitializerApplicationContextInitializer是Spring框架提供的一个扩展点允许我们在ApplicationContext刷新之前执行一些初始化逻辑。这正是我们需要的——在配置文件被解析之前就准备好加密器。import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; public class CustomEncryptorInitializer implements ApplicationContextInitializerConfigurableApplicationContext { Override public void initialize(ConfigurableApplicationContext applicationContext) { // 在BeanFactory中手动注册单例Bean applicationContext.getBeanFactory().registerSingleton( sm4Encryptor, new Sm4Encryptor() ); // 可选添加一些日志输出便于调试 System.out.println(自定义SM4加密器已提前注册到Spring容器); } }2.2 配置Spring.factories文件要让Spring Boot自动发现并加载我们的ApplicationContextInitializer需要在resources/META-INF/目录下创建spring.factories文件# src/main/resources/META-INF/spring.factories org.springframework.context.ApplicationContextInitializer\ com.yourpackage.config.CustomEncryptorInitializer2.3 完整的SM4加密器实现让我们完善一下SM4加密器的实现。这里我使用Hutool工具库简化SM4算法的使用import cn.hutool.crypto.Mode; import cn.hutool.crypto.Padding; import cn.hutool.crypto.symmetric.SM4; import org.jasypt.encryption.StringEncryptor; import org.springframework.beans.factory.annotation.Value; import java.nio.charset.StandardCharsets; public class Sm4Encryptor implements StringEncryptor { private final SM4 sm4; /** * 通过构造函数注入密钥和IV * 可以从环境变量或配置文件中读取 */ public Sm4Encryptor( Value(${jasypt.encryptor.sm4.key:1234567890123456}) String key, Value(${jasypt.encryptor.sm4.iv:1234567890123456}) String iv) { // 确保密钥和IV长度符合SM4要求16字节 byte[] keyBytes ensureLength(key.getBytes(StandardCharsets.UTF_8), 16); byte[] ivBytes ensureLength(iv.getBytes(StandardCharsets.UTF_8), 16); // 使用CBC模式PKCS5Padding填充 this.sm4 new SM4(Mode.CBC, Padding.PKCS5Padding, keyBytes, ivBytes); } /** * 无参构造函数使用默认密钥仅用于测试环境 */ public Sm4Encryptor() { this(1234567890123456, 1234567890123456); } private byte[] ensureLength(byte[] input, int requiredLength) { if (input.length requiredLength) { return input; } byte[] result new byte[requiredLength]; System.arraycopy(input, 0, result, 0, Math.min(input.length, requiredLength)); return result; } Override public String encrypt(String message) { if (message null) { return null; } return sm4.encryptHex(message); } Override public String decrypt(String encryptedMessage) { if (encryptedMessage null) { return null; } return sm4.decryptStr(encryptedMessage); } /** * 测试方法可用于验证加密解密功能 */ public static void main(String[] args) { Sm4Encryptor encryptor new Sm4Encryptor(); String original Hello, Jasypt with SM4!; String encrypted encryptor.encrypt(original); String decrypted encryptor.decrypt(encrypted); System.out.println(原始文本: original); System.out.println(加密结果: encrypted); System.out.println(解密结果: decrypted); System.out.println(验证结果: original.equals(decrypted)); } }2.4 配置文件的调整使用ApplicationContextInitializer方式后我们不再需要原来的Configuration配置类。但需要在配置文件中明确指定要使用的加密器Bean名称# application.yml jasypt: encryptor: bean: sm4Encryptor # 与ApplicationContextInitializer中注册的名称一致 # 可选配置SM4专用的密钥和IV sm4: key: ${SM4_ENCRYPT_KEY:1234567890123456} iv: ${SM4_ENCRYPT_IV:1234567890123456} # 数据库配置示例使用加密密码 spring: datasource: url: jdbc:mysql://localhost:3306/your_database username: your_username password: ENC(你的加密后的数据库密码) driver-class-name: com.mysql.cj.jdbc.Driver # Redis配置示例 redis: host: localhost port: 6379 password: ENC(你的加密后的Redis密码) database: 0 # 其他需要加密的敏感配置 custom: api: key: ENC(加密的API密钥) secret: ENC(加密的API密钥)2.5 这种方案的优缺点分析优点完全解决了Bean加载时机问题实现相对简单逻辑清晰适用于所有Spring Boot版本不依赖特定的Spring Boot启动顺序缺点需要手动创建spring.factories文件加密器Bean的依赖注入需要特殊处理调试时可能不太直观3. 解决方案二调整Bean定义顺序与依赖关系如果你不想使用ApplicationContextInitializer或者项目结构不允许修改META-INF/spring.factories还有另一种思路通过调整Bean的依赖关系确保加密器在其他Bean之前初始化。3.1 使用DependsOn和Order注解Spring提供了DependsOn注解来显式声明Bean之间的依赖关系。我们可以利用这个特性让那些需要加密配置的Bean显式依赖于加密器BeanConfiguration public class JasyptConfig { /** * 使用DependsOn确保这个Bean最早初始化 * 使用Order(Ordered.HIGHEST_PRECEDENCE)设置最高优先级 */ Bean(name sm4Encryptor) Order(Ordered.HIGHEST_PRECEDENCE) public StringEncryptor stringEncryptor() { return new Sm4Encryptor(); } } // 在其他需要加密配置的配置类中 Configuration DependsOn(sm4Encryptor) // 显式声明依赖 public class DatabaseConfig { Value(${spring.datasource.password}) private String dbPassword; Bean public DataSource dataSource() { // 这里使用的dbPassword已经是解密后的值 HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/test); config.setUsername(root); config.setPassword(dbPassword); // 自动解密 return new HikariDataSource(config); } }3.2 使用Lazy注解延迟初始化另一种思路是延迟那些依赖加密配置的Bean的初始化直到加密器Bean准备好Configuration public class JasyptConfig { Bean(sm4Encryptor) public StringEncryptor stringEncryptor() { return new Sm4Encryptor(); } } Component public class EncryptedPropertiesValidator { private final Environment environment; public EncryptedPropertiesValidator(Environment environment) { this.environment environment; // 验证加密属性是否能够正确解密 validateEncryptedProperties(); } private void validateEncryptedProperties() { // 这里可以验证关键配置是否能够正确解密 String dbPassword environment.getProperty(spring.datasource.password); System.out.println(数据库密码配置验证通过); } } Configuration public class LateInitializationConfig { /** * 使用Lazy延迟初始化确保加密器先准备好 */ Bean Lazy public SomeService someService( Value(${custom.encrypted.property}) String encryptedValue) { // 这个Bean的初始化会延迟到加密器可用之后 return new SomeService(encryptedValue); } }3.3 配置属性加载顺序调整我们还可以通过调整配置文件的加载顺序让某些配置在加密器初始化之后才加载Configuration public class JasyptConfig { Bean(sm4Encryptor) public StringEncryptor stringEncryptor() { return new Sm4Encryptor(); } } /** * 使用PropertySource指定配置加载顺序 * 注意这个配置类要在JasyptConfig之后加载 */ Configuration PropertySource( value classpath:encrypted.properties, // 使用较低的order值确保在其他配置之后加载 name encryptedProperties ) Order(Ordered.LOWEST_PRECEDENCE) public class EncryptedPropertiesConfig { // 这个配置类中的属性会在加密器初始化后才解析 }3.4 这种方案的适用场景调整Bean定义顺序的方案更适合以下场景项目结构简单Bean数量不多依赖关系清晰能够控制所有配置类的加载顺序不需要加密的配置属性较多只有少数几个敏感配置需要加密团队对Spring Bean生命周期有深入理解4. 解决方案三自定义PropertySource处理机制对于更复杂的场景我们可以深入到Spring的PropertySource机制实现一个完全自定义的加密属性解析流程。4.1 实现自定义的EncryptablePropertySourceSpring Boot的属性解析机制基于PropertySource体系。我们可以实现一个自定义的PropertySource在属性被访问时才进行解密import org.springframework.core.env.PropertySource; import org.springframework.core.env.StandardEnvironment; import org.jasypt.encryption.StringEncryptor; import java.util.HashMap; import java.util.Map; /** * 延迟解密的PropertySource实现 * 只有在属性被访问时才进行解密避免启动时的时序问题 */ public class LazyDecryptPropertySource extends PropertySourceMapString, Object { private final StringEncryptor encryptor; private final PropertySource? originalSource; private final MapString, String decryptedCache new HashMap(); public LazyDecryptPropertySource(PropertySource? originalSource, StringEncryptor encryptor) { super(lazyDecryptPropertySource, new HashMap()); this.originalSource originalSource; this.encryptor encryptor; } Override public Object getProperty(String name) { Object value originalSource.getProperty(name); if (value instanceof String) { String stringValue (String) value; // 检查是否是加密的值以ENC(开头以)结尾 if (isEncryptedValue(stringValue)) { return decryptCached(name, stringValue); } } return value; } private boolean isEncryptedValue(String value) { return value ! null value.startsWith(ENC() value.endsWith()); } private String decryptCached(String name, String encryptedValue) { // 先从缓存中查找 if (decryptedCache.containsKey(name)) { return decryptedCache.get(name); } // 提取加密内容去掉ENC()包装 String encryptedContent encryptedValue .substring(4, encryptedValue.length() - 1); // 解密 String decrypted encryptor.decrypt(encryptedContent); // 放入缓存 decryptedCache.put(name, decrypted); return decrypted; } Override public boolean containsProperty(String name) { return originalSource.containsProperty(name); } }4.2 创建PropertySourcePostProcessor我们需要一个后置处理器来包装原有的PropertySourceimport org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; import org.jasypt.encryption.StringEncryptor; public class EncryptablePropertySourcePostProcessor implements BeanFactoryPostProcessor { Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { // 获取加密器Bean StringEncryptor encryptor beanFactory.getBean(StringEncryptor.class); // 获取环境中的PropertySources ConfigurableEnvironment environment beanFactory.getBean(ConfigurableEnvironment.class); MutablePropertySources propertySources environment.getPropertySources(); // 包装所有的PropertySource for (PropertySource? originalSource : propertySources) { if (shouldWrap(originalSource)) { LazyDecryptPropertySource wrappedSource new LazyDecryptPropertySource(originalSource, encryptor); // 替换原有的PropertySource propertySources.replace(originalSource.getName(), wrappedSource); } } } private boolean shouldWrap(PropertySource? source) { // 这里可以根据需要决定包装哪些PropertySource // 例如只包装application.properties/yml相关的源 String name source.getName(); return name.contains(application) || name.contains(ConfigurationProperties); } }4.3 注册后置处理器在配置类中注册我们的后置处理器Configuration public class JasyptAdvancedConfig { Bean public static EncryptablePropertySourcePostProcessor encryptablePropertySourcePostProcessor() { return new EncryptablePropertySourcePostProcessor(); } Bean(jasyptStringEncryptor) // 使用默认的Bean名称 public StringEncryptor stringEncryptor() { // 这里可以创建任何自定义的加密器 return new Sm4Encryptor(); } }4.4 配置文件的对应调整使用这种方案时配置文件可以保持最简形式# application.yml # 注意这里不需要指定jasypt.encryptor.bean # 因为我们会使用默认的Bean名称jasyptStringEncryptor spring: datasource: url: jdbc:mysql://localhost:3306/test username: root password: ENC(加密后的密码) # 自动被我们的PropertySource解密4.5 这种方案的深度分析自定义PropertySource处理机制是最灵活但也最复杂的解决方案。它的核心优势在于完全控制解密时机可以在属性被访问时才解密避免启动时的时序问题兼容性好不依赖Jasypt的自动配置机制可以与其他加密方案共存可扩展性强可以轻松添加自定义的解密逻辑、缓存机制等但相应的这种方案也有明显的缺点实现复杂度高需要深入理解Spring的PropertySource机制维护成本高自定义的代码需要随着Spring版本更新而调整可能影响性能每次属性访问都需要检查是否需要解密5. 实战国密SM4加密器的完整实现与测试无论选择哪种解决方案一个健壮的SM4加密器实现都是基础。让我们深入看看如何实现一个生产可用的SM4加密器。5.1 增强的SM4加密器实现import cn.hutool.core.util.HexUtil; import cn.hutool.crypto.Mode; import cn.hutool.crypto.Padding; import cn.hutool.crypto.symmetric.SM4; import org.jasypt.encryption.StringEncryptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import javax.annotation.PostConstruct; import java.nio.charset.StandardCharsets; import java.util.Base64; /** * 增强的SM4加密器实现 * 支持多种密钥来源和加密模式 */ public class EnhancedSm4Encryptor implements StringEncryptor { private static final Logger logger LoggerFactory.getLogger(EnhancedSm4Encryptor.class); private SM4 sm4; private final String algorithm SM4/CBC/PKCS5Padding; // 配置参数支持从环境变量或配置文件读取 Value(${jasypt.encryptor.sm4.key:#{null}}) private String configKey; Value(${jasypt.encryptor.sm4.iv:#{null}}) private String configIv; Value(${jasypt.encryptor.sm4.key-file:#{null}}) private String keyFile; Value(${jasypt.encryptor.sm4.mode:CBC}) private String mode; Value(${jasypt.encryptor.sm4.padding:PKCS5Padding}) private String padding; PostConstruct public void init() { try { // 1. 确定密钥来源配置文件 环境变量 默认值 byte[] keyBytes resolveKey(); byte[] ivBytes resolveIv(); // 2. 验证密钥长度 validateKeyLength(keyBytes, 16); // SM4需要16字节密钥 // 3. 创建SM4实例 Mode sm4Mode Mode.valueOf(mode.toUpperCase()); Padding sm4Padding Padding.valueOf(padding.toUpperCase()); this.sm4 new SM4(sm4Mode, sm4Padding, keyBytes, ivBytes); logger.info(SM4加密器初始化成功模式{}填充{}, mode, padding); // 4. 自检加密解密测试 selfTest(); } catch (Exception e) { logger.error(SM4加密器初始化失败, e); throw new IllegalStateException(SM4加密器初始化失败, e); } } private byte[] resolveKey() { // 优先级配置文件 环境变量 默认值 if (configKey ! null !configKey.trim().isEmpty()) { return decodeKey(configKey); } String envKey System.getenv(SM4_ENCRYPT_KEY); if (envKey ! null !envKey.trim().isEmpty()) { return decodeKey(envKey); } // 生产环境应该避免使用硬编码的默认密钥 logger.warn(使用默认SM4密钥生产环境请通过配置或环境变量设置密钥); return default_sm4_key_16.getBytes(StandardCharsets.UTF_8); } private byte[] resolveIv() { if (configIv ! null !configIv.trim().isEmpty()) { return decodeKey(configIv); } String envIv System.getenv(SM4_ENCRYPT_IV); if (envIv ! null !envIv.trim().isEmpty()) { return decodeKey(envIv); } // 使用与密钥相同的默认值实际生产应该使用不同的IV return default_sm4_iv_16.getBytes(StandardCharsets.UTF_8); } private byte[] decodeKey(String key) { // 支持多种格式Base64、Hex、直接字符串 if (key.startsWith(base64:)) { return Base64.getDecoder().decode(key.substring(7)); } else if (key.startsWith(hex:)) { return HexUtil.decodeHex(key.substring(4)); } else { // 直接作为字符串确保长度为16字节 byte[] bytes key.getBytes(StandardCharsets.UTF_8); return ensureLength(bytes, 16); } } private byte[] ensureLength(byte[] input, int requiredLength) { if (input.length requiredLength) { return input; } byte[] result new byte[requiredLength]; int length Math.min(input.length, requiredLength); System.arraycopy(input, 0, result, 0, length); // 如果长度不足用0填充 for (int i length; i requiredLength; i) { result[i] 0; } return result; } private void validateKeyLength(byte[] key, int expectedLength) { if (key.length ! expectedLength) { throw new IllegalArgumentException( String.format(SM4密钥长度必须为%d字节实际为%d字节, expectedLength, key.length)); } } private void selfTest() { try { String testText SM4加密器自检文本; String encrypted encrypt(testText); String decrypted decrypt(encrypted); if (!testText.equals(decrypted)) { throw new IllegalStateException(SM4加密器自检失败解密结果与原文不一致); } logger.debug(SM4加密器自检通过); } catch (Exception e) { throw new IllegalStateException(SM4加密器自检失败, e); } } Override public String encrypt(String message) { if (message null) { return null; } try { return sm4.encryptHex(message); } catch (Exception e) { logger.error(SM4加密失败, e); throw new RuntimeException(加密失败, e); } } Override public String decrypt(String encryptedMessage) { if (encryptedMessage null) { return null; } try { return sm4.decryptStr(encryptedMessage); } catch (Exception e) { logger.error(SM4解密失败, e); throw new RuntimeException(解密失败, e); } } /** * 获取加密器信息用于监控和调试 */ public String getEncryptorInfo() { return String.format(SM4加密器[模式%s, 填充%s, 状态%s], mode, padding, sm4 ! null ? 就绪 : 未初始化); } }5.2 完整的测试用例为了确保我们的加密器在各种场景下都能正常工作编写全面的测试用例非常重要import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeEach; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.TestPropertySource; import static org.junit.jupiter.api.Assertions.*; SpringBootTest TestPropertySource(properties { jasypt.encryptor.beansm4Encryptor, jasypt.encryptor.sm4.keymy_secret_key_16, jasypt.encryptor.sm4.ivmy_initial_vec_16 }) public class Sm4EncryptorTest { private EnhancedSm4Encryptor encryptor; BeforeEach public void setUp() { encryptor new EnhancedSm4Encryptor(); // 这里需要模拟Spring的Value注入 // 实际项目中可以通过Autowired注入 } Test public void testBasicEncryptionDecryption() { String original Hello, Jasypt with SM4!; String encrypted encryptor.encrypt(original); String decrypted encryptor.decrypt(encrypted); assertNotNull(encrypted); assertNotEquals(original, encrypted); assertEquals(original, decrypted); System.out.println(基础加密解密测试通过); } Test public void testEmptyString() { String original ; String encrypted encryptor.encrypt(original); String decrypted encryptor.decrypt(encrypted); assertEquals(original, decrypted); System.out.println(空字符串测试通过); } Test public void testNullValue() { assertNull(encryptor.encrypt(null)); assertNull(encryptor.decrypt(null)); System.out.println(null值测试通过); } Test public void testLongText() { StringBuilder longText new StringBuilder(); for (int i 0; i 1000; i) { longText.append(这是一段测试文本用于测试长文本的加密解密。); } String original longText.toString(); String encrypted encryptor.encrypt(original); String decrypted encryptor.decrypt(encrypted); assertEquals(original, decrypted); System.out.println(长文本测试通过); } Test public void testSpecialCharacters() { String[] testCases { 特殊字符!#$%^*(), 中文测试你好世界, 混合Hello 123 你好 #$, 换行符\n第二行, 制表符\t缩进 }; for (String original : testCases) { String encrypted encryptor.encrypt(original); String decrypted encryptor.decrypt(encrypted); assertEquals(original, decrypted, 特殊字符测试失败 original); } System.out.println(特殊字符测试通过); } Test public void testPerformance() { int iterations 1000; String testText 性能测试文本; long startTime System.currentTimeMillis(); for (int i 0; i iterations; i) { String encrypted encryptor.encrypt(testText i); encryptor.decrypt(encrypted); } long endTime System.currentTimeMillis(); long totalTime endTime - startTime; double avgTime (double) totalTime / iterations; System.out.printf(性能测试%d次加密解密总耗时%dms平均%.3fms/次%n, iterations, totalTime, avgTime); // 性能要求平均每次操作小于10ms assertTrue(avgTime 10, 性能不达标平均耗时 avgTime ms); } }5.3 集成测试模拟真实Spring Boot应用import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.core.env.Environment; import static org.junit.jupiter.api.Assertions.*; SpringBootTest(classes TestApplication.class) public class JasyptIntegrationTest { Autowired private Environment environment; Autowired private StringEncryptor stringEncryptor; Test public void testEncryptedPropertyResolution() { // 测试配置文件中加密属性的解析 String dbPassword environment.getProperty(spring.datasource.password); assertNotNull(dbPassword, 数据库密码不应为null); assertNotEquals(, dbPassword.trim(), 数据库密码不应为空); // 验证密码不是加密形式应该已经被解密 assertFalse(dbPassword.startsWith(ENC(), 数据库密码应该已经被解密); assertFalse(dbPassword.endsWith()), 数据库密码应该已经被解密); System.out.println(加密属性解析测试通过数据库密码 (dbPassword.length() 5 ? *** dbPassword.substring(dbPassword.length() - 3) : ***)); } Test public void testDirectEncryptionDecryption() { // 测试直接使用加密器 String original sensitive_data_123; String encrypted stringEncryptor.encrypt(original); String decrypted stringEncryptor.decrypt(encrypted); assertEquals(original, decrypted); System.out.println(直接加密解密测试通过); } Test public void testMultipleEncryptionConsistency() { // 测试多次加密同一内容结果是否不同由于IV随机 String original test_consistency; String encrypted1 stringEncryptor.encrypt(original); String encrypted2 stringEncryptor.encrypt(original); assertNotEquals(encrypted1, encrypted2, 由于CBC模式的随机IV两次加密结果应该不同); // 但解密后应该相同 assertEquals(original, stringEncryptor.decrypt(encrypted1)); assertEquals(original, stringEncryptor.decrypt(encrypted2)); System.out.println(加密一致性测试通过); } } // 测试用的Spring Boot应用 SpringBootApplication EnableEncryptableProperties class TestApplication { Bean(sm4Encryptor) public StringEncryptor stringEncryptor() { return new EnhancedSm4Encryptor(); } public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } }5.4 配置文件示例最后让我们看看完整的配置文件应该如何配置# application.yml - 开发环境配置示例 jasypt: encryptor: bean: sm4Encryptor # 指定使用我们的SM4加密器 sm4: # 开发环境可以使用简单的密钥 key: development_key_16 iv: development_iv_16 mode: CBC padding: PKCS5Padding spring: datasource: url: jdbc:mysql://localhost:3306/dev_db username: dev_user password: ENC(加密后的开发数据库密码) hikari: maximum-pool-size: 10 minimum-idle: 5 connection-timeout: 30000 redis: host: localhost port: 6379 password: ENC(加密后的Redis密码) database: 0 # 其他可能需要加密的配置 security: oauth2: client: registration: github: client-id: your_client_id client-secret: ENC(加密的GitHub客户端密钥) mail: host: smtp.gmail.com port: 587 username: your_emailgmail.com password: ENC(加密的邮箱密码) # 自定义的加密配置 app: encryption: enabled: true algorithm: SM4 # 敏感API密钥 third-party: api-key: ENC(加密的第三方API密钥) api-secret: ENC(加密的第三方API密钥)# application-prod.yml - 生产环境配置示例 jasypt: encryptor: bean: sm4Encryptor sm4: # 生产环境通过环境变量注入密钥不在配置文件中硬编码 key: ${SM4_ENCRYPT_KEY:} iv: ${SM4_ENCRYPT_IV:} mode: CBC padding: PKCS5Padding spring: datasource: url: ${DB_URL} username: ${DB_USERNAME} password: ENC(${ENCRYPTED_DB_PASSWORD}) # 密码在部署时加密 hikari: maximum-pool-size: 20 minimum-idle: 10 # 生产环境的其他配置...5.5 部署和密钥管理建议在实际生产环境中密钥管理是安全的核心。以下是一些建议密钥存储永远不要在代码或配置文件中硬编码密钥。使用环境变量、密钥管理服务如AWS KMS、HashiCorp Vault或容器编排平台的secret管理功能。密钥轮换定期轮换加密密钥并确保新旧密钥在过渡期内都能解密数据。多环境配置为开发、测试、生产环境使用不同的密钥。监控和告警监控加密解密操作的成功率设置异常告警。备份和恢复确保密钥的安全备份并建立密钥丢失的恢复流程。通过以上完整的实现和测试我们不仅解决了Bean not found的问题还构建了一个健壮、可测试、可维护的SM4加密解决方案。无论你的项目规模如何这套方案都能提供可靠的配置加密能力。

相关新闻

为什么你的订单匹配引擎总卡在12.8μs?揭秘头部券商正在封测的预分配分级内存池,已通过ISO 20022合规压力验证

为什么你的订单匹配引擎总卡在12.8μs?揭秘头部券商正在封测的预分配分级内存池,已通过ISO 20022合规压力验证

第一章:为什么你的订单匹配引擎总卡在12.8μs?12.8 微秒不是魔法数字,而是现代高频交易系统中一个反复出现的性能瓶颈阈值——它恰好略高于主流 Linux 内核下 epoll_wait 的最小可观测延迟(典型为 12.3–12.7μs)&…

2026/6/26 18:50:30 阅读更多 →
避开这些坑!Adams-Matlab联合仿真中的5个高频错误及解决方案

避开这些坑!Adams-Matlab联合仿真中的5个高频错误及解决方案

避开这些坑!Adams-Matlab联合仿真中的5个高频错误及解决方案 在工程仿真领域,Adams与Matlab的联合仿真堪称多体动力学与控制策略验证的“黄金搭档”。它能将Adams精准的物理模型与Matlab/Simulink强大的控制算法设计能力无缝衔接,为复杂机电系…

2026/6/26 19:00:23 阅读更多 →
RexUniNLU在智能投顾系统应用:财经新闻实体识别+情绪传导路径建模

RexUniNLU在智能投顾系统应用:财经新闻实体识别+情绪传导路径建模

RexUniNLU在智能投顾系统应用:财经新闻实体识别情绪传导路径建模 1. 引言 想象一下,你是一位基金经理,每天早晨打开电脑,面对的是海量的财经新闻、公司公告和社交媒体讨论。你需要快速从中找出关键信息:哪家公司发布…

2026/6/26 19:17:44 阅读更多 →

最新新闻

浅析正则表达式—(原理篇)

浅析正则表达式—(原理篇)

其实这篇文章很久之前就应该发出来,由于种种原因没有发出来,如果这篇文章中有错误,还请大家指出,小弟并改正之,没有学不会的东西,只有不想学的东西,只要功夫深,铁杵磨成针&#xff0…

2026/7/3 21:15:33 阅读更多 →
当你在深夜想保存那个在线课程时:一个M3U8下载器的故事

当你在深夜想保存那个在线课程时:一个M3U8下载器的故事

当你在深夜想保存那个在线课程时:一个M3U8下载器的故事 【免费下载链接】m3u8-downloader 一个M3U8 视频下载(M3U8 downloader)工具。跨平台: 提供windows、linux、mac三大平台可执行文件,方便直接使用。 项目地址: https://gitcode.com/gh_mirrors/m3u8d/m3u8-d…

2026/7/3 21:13:33 阅读更多 →
TwitchNoSub:解锁Twitch订阅专属内容的完整指南

TwitchNoSub:解锁Twitch订阅专属内容的完整指南

TwitchNoSub:解锁Twitch订阅专属内容的完整指南 【免费下载链接】TwitchNoSub An extension to watch sub only VOD on Twitch 项目地址: https://gitcode.com/gh_mirrors/tw/TwitchNoSub 你是否曾经在Twitch上发现一个精彩的直播回放,却因为&quo…

2026/7/3 21:13:33 阅读更多 →
PyTorch模型性能优化实战:从数据加载到部署

PyTorch模型性能优化实战:从数据加载到部署

1. PyTorch模型性能优化全景解析在深度学习项目实践中,模型性能优化是每个从业者必须掌握的硬核技能。最近接手的一个工业级图像分类项目让我深刻体会到:当数据集规模达到千万级,即使使用RTX 4090这样的顶级显卡,未经优化的PyTorc…

2026/7/3 21:05:29 阅读更多 →
MuleSoft企业级AI编排:让大模型听懂ERP与CRM

MuleSoft企业级AI编排:让大模型听懂ERP与CRM

1. 项目概述:当企业级集成平台遇上大语言模型,不是叠加,而是重定义工作流“AI Orchestration in Action: How MuleSoft and LLMs Fuel the Future of Enterprise AI”——这个标题里藏着一个正在发生的、静默却剧烈的范式转移。它说的不是“用…

2026/7/3 21:05:29 阅读更多 →
STM32与TI降压转换器的高效电源管理方案

STM32与TI降压转换器的高效电源管理方案

1. 项目背景与硬件选型解析在嵌入式电源管理领域,DC-DC降压转换是基础但至关重要的技术环节。本次项目采用171010550电源管理IC与STM32F215ZG微控制器的组合方案,这个搭配在工业控制领域颇具代表性。171010550是TI(德州仪器)旗下的…

2026/7/3 21:03:28 阅读更多 →

日新闻

Nginx防御TLS重协商攻击实战:从原理到配置与监控

Nginx防御TLS重协商攻击实战:从原理到配置与监控

1. 项目概述:为什么TLS重协商攻击至今仍需警惕十多年前的CVE-2011-1473,一个关于TLS/SSL协议重协商机制的漏洞,现在提起来还有必要吗?很多运维和开发朋友可能会觉得,这都老掉牙了,现代服务器和客户端不都默…

2026/7/3 0:03:59 阅读更多 →
华为防火墙双通道远程管理实战:Web与SSH配置详解

华为防火墙双通道远程管理实战:Web与SSH配置详解

1. 项目概述:为什么需要双通道远程管理防火墙?在任何一个稍具规模的企业网络里,防火墙都是那个默默守护在边界的关键角色。作为网络工程师,我们不可能每次都跑到机房,插上console线去配置它。远程管理能力,…

2026/7/3 0:03:59 阅读更多 →
AD74413R与PIC18F65K40的高精度工业数据采集方案

AD74413R与PIC18F65K40的高精度工业数据采集方案

1. 项目概述:AD74413R与PIC18F65K40的协同工作在工业自动化和精密测量领域,同时实现高精度模数转换(ADC)和数模转换(DAC)功能是许多复杂系统的核心需求。AD74413R作为一款四通道可配置模拟输入/输出器件,与PIC18F65K40微控制器的组合&#xf…

2026/7/3 0:05:59 阅读更多 →

周新闻

月新闻