• 如何优雅的加密配置文件中的敏感信息


    为什么要加密配置文件信息

    我们平时的项目中,会在配置文件中配置一些敏感信息,比如配置数据库账号、密码等信息。如果我们将配置文件与代码一起打包,别人拿到jar包后很有可能反编译jar,从而获取里面的配置文件信息。如果有人对数据库信息恶意破坏,那么就会产生不可估量的损失。

    如上图,我们将jar包反编译会看到application-*.yml相关文件的信息,里面就包含一些敏感用户名密码信息。

    因此我们需要将这些敏感信息进行加密。

    SpringBoot工程中的数据库地址,密码为例。

    开源插件推荐

    我们可以自己开发加密功能,这里我引入一个开源插件。

    就是这个大佬的项目。

    该项目github地址:

    https://github.com/ulisesbocchio/jasypt-spring-boot

    使用介绍

    具体的使用方式可以参考项目里面的说明README.md,下面我们简单来使用一下:

    1. <dependency>
    2.     <groupId>com.github.ulisesbocchio</groupId>
    3.     <artifactId>jasypt-spring-boot-starter</artifactId>
    4.     <version>2.1.0</version>
    5. </dependency>

    引入框架后,我们配置文件数据库信息就可以用加密的形式来配置。

    这里使用了ENC(*)用于识别是否需要加密。

    同时还要在application文件中中配置密钥:

    当然更加安全的方法是将密匙加载在环境变量中:

    这样在启动系统时,执行如下命令即可:

    java -jar -Djasypt.encryptor.password=${JASYPT_PASSWORD} xxx.jar
    

    那么加密的数据是怎么获取的呢,我们需要将真实的地址和密码行进加密,加密代码如下:

    运行上述代码即可获取加密后的数据库信息。

    此框架的逻辑是,在加载配置文件时,做拦截操作,当发现有ENC包裹的字符串,会对其进行解密操作。

    源码分析

    我们看源码中

    JasyptSpringBootAutoConfiguration类,系统在启动时会加载这个类。

    1. /**
    2.  * @author Ulises Bocchio
    3.  */
    4. @Configuration
    5. @Import(EnableEncryptablePropertiesConfiguration.class)
    6.   public class JasyptSpringBootAutoConfiguration {
    7. }

    这里加载了类EnableEncryptablePropertiesConfiguration。我们来看这个类的代码。

    1. @Configuration
    2. @Import({EncryptablePropertyResolverConfiguration.class, CachingConfiguration.class})
    3. @Slf4j
    4. public class EnableEncryptablePropertiesConfiguration {
    5.     @Bean
    6.     public static EnableEncryptablePropertiesBeanFactoryPostProcessor enableEncryptablePropertySourcesPostProcessor(final ConfigurableEnvironment environment, EncryptablePropertySourceConverter converter) {
    7.         return new EnableEncryptablePropertiesBeanFactoryPostProcessor(environment, converter);
    8.     }
    9. }

    这里类EnableEncryptablePropertiesConfiguration注册了一个类EnableEncryptablePropertiesBeanFactoryPostProcessor

    如上图,EnableEncryptablePropertiesBeanFactoryPostProcessor类用于加载配置文件。

    这个类中的构造器中传入了两个参数:environmentconverter。其中converter就是对配置文件做解析处理用的。

    EncryptablePropertySourceConverter类中,

    1. @SuppressWarnings({"unchecked""rawtypes"})
    2.     private <T> PropertySource<T> instantiatePropertySource(PropertySource<T> propertySource) {
    3.         PropertySource<T> encryptablePropertySource;
    4.         if (needsProxyAnyway(propertySource)) {
    5.             encryptablePropertySource = proxyPropertySource(propertySource);
    6.         } else if (propertySource instanceof SystemEnvironmentPropertySource) {
    7.             encryptablePropertySource = (PropertySource<T>) new EncryptableSystemEnvironmentPropertySourceWrapper((SystemEnvironmentPropertySource) propertySource, propertyResolver, propertyFilter);
    8.         } else if (propertySource instanceof MapPropertySource) {
    9.             encryptablePropertySource = (PropertySource<T>) new EncryptableMapPropertySourceWrapper((MapPropertySource) propertySource, propertyResolver, propertyFilter);
    10.         } else if (propertySource instanceof EnumerablePropertySource) {
    11.             encryptablePropertySource = new EncryptableEnumerablePropertySourceWrapper<>((EnumerablePropertySource) propertySource, propertyResolver, propertyFilter);
    12.         } else {
    13.             encryptablePropertySource = new EncryptablePropertySourceWrapper<>(propertySource, propertyResolver, propertyFilter);
    14.         }
    15.         return encryptablePropertySource;
    16.     }

    从上面代码看,我们发现EncryptableMapPropertySourceWrapper类和EncryptableEnumerablePropertySourceWrapper类出现频繁,这两个类其实就是用于解密的类。

    这两个类都重写了getProperty方法

    1. @Override
    2. public Object getProperty(String name) {
    3.  return encryptableDelegate.getProperty(name);
    4. }

    我们来看看里面的getProperty()方法。

    最终调到EncryptablePropertySource#getProperty

    1. default Object getProperty(EncryptablePropertyResolver resolver, EncryptablePropertyFilter filter, PropertySource<T> sourceString name) {
    2.     Object value = source.getProperty(name);
    3.     if (value != null && filter.shouldInclude(source, name) && value instanceof String) {
    4.         String stringValue = String.valueOf(value);
    5.         return resolver.resolvePropertyValue(stringValue);
    6.     }
    7.     return value;
    8. }

    我们在来看看resolver.resolvePropertyValue(stringValue)方法:

    这里对配置文件中的value做了筛选:

    1. @Override
    2. public boolean isEncrypted(String property) {
    3.     if (property == null) {
    4.      return false;
    5.     }
    6.     final String trimmedValue = property.trim();
    7.     return (trimmedValue.startsWith(prefix) &&
    8.     trimmedValue.endsWith(suffix));
    9. }

    筛选的是以ENC包裹的值。

    1. private String prefix = "ENC(";
    2. private String suffix = ")";

    resolver.resolvePropertyValue(stringValue)方法中,做了几件事:

    1.获取ENC包裹的字符value

    2.截取括号里面的值

    3.占位符替换

    4.解码

    我们调试看看,启动系统:

    这里会将配置文件中ENC包裹的value值进行解码:

    解码操作:

    将解码后的值写回到缓存ConcurrentMapCache中:

    这样spring后面读取的value就是解码后的value了。

    今天的文章就写到这里了,如果对你有帮助,欢迎点赞+转发。

  • 相关阅读:
    解决nacos集群搭建,服务注册失败
    (13)Latex:基于ΤΕΧ的自动排版系统——写论文必备
    【计算机基础知识10】解析黑窗口CMD:认识CMD及常见命令
    225. 用队列实现栈-C语言
    第1章 算法和数据结构
    业务架构·应用架构·数据架构实战~战略驱动的业务架构设计
    Linux文件-内存映射mmap
    产品代码都给你看了,可别再说不会DDD(三):战略设计
    java计算机毕业设计ssm兴趣班和延时班管理系统(源码+系统+mysql数据库+Lw文档)
    如何用树莓派搭建一台永久运行的个人服务器?
  • 原文地址:https://blog.csdn.net/wujialv/article/details/126437113