前语

咱们在SpringBoot项目当中,会把数据库的用户名暗码等装备直接放在yaml或许properties文件中,这样维护数据库的暗码等灵敏信息显然是有一定危险的,假如相关的装备文件被有心之人拿到,必定会给项目造成一定的安全危险;所以为了防止明文暗码被直接看到,咱们有必要给这些灵敏信息做一层加密处理,也便是说,咱们的装备文件中装备的都是加密后的暗码,在真实需求获取暗码的时分再解密出来,这样的话就能很大程度上降低暗码被走漏的危险;

示例展示

咱们来看一下这个装备:

spring:
 # 数据库链接装备
  datasource:
   url: jdbc:mysql://xx.xx.xx.xx:3306/database
   driver-class-name: com.mysql.cj.jdbc.Driver
   username: root
   password: "123456"

咱们上述的装备spring.datasource.password对应的值为123456,这么灵敏的信息直接放在装备文件中很不合适,咱们要做的便是对应的值改成一个加密的密文,如下:

spring:
 # 数据库链接装备
  datasource:
   url: jdbc:mysql://xx.xx.xx.xx:3306/database
   driver-class-name: com.mysql.cj.jdbc.Driver
   username: root
   password: "AES(DzANBAhBWXxZqAOsagIBCoaw8FV4gYRbid7G70UEM24=)"

这样的话,即便该装备文件被有心之人拿去,也不知道真实的数据库暗码是啥,也就无法构成对项目的损害危险;

原理解析

咱们为了完成这个功用,需求了解Spring的相关扩展点以及对应的数据加解密常识,咱们先来看看咱们应该经过Spring的哪个扩展点进行切入;

咱们想要阻拦装备数据的话,能够经过完成自定义的BeanFactoryPostProcessor来处理:

public class PropertySourcePostProcessor implements BeanFactoryPostProcessor {
​
  private ConfigurableEnvironment environment;
​
  public PropertySourcePostProcessor(ConfigurableEnvironment environment) {
    this.environment = environment;
  }
​
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
  // 从ConfigurableEnvironment中取出一切的装备数据
    MutablePropertySources propertySources = this.environment.getPropertySources();
    propertySources.stream()
    // 过滤不需求包装的目标
        .filter(s -> !noWrapPropertySource(s))
        // 包装一切的PropertySource
        .map(s -> new EncryPropertySource(s))
        .collect(Collectors.toList())
        // 替换掉propertySources中的PropertySource
        .forEach(wrap -> propertySources.replace(wrap.getName(), wrap));
  }
​
  private boolean noWrapPropertySource(PropertySource propertySource) {
    return propertySource instanceof EncryPropertySource || StringUtils.equalsAny(propertySource.getClass().getName(), "org.springframework.core.env.PropertySource$StubPropertySource", "org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertySource");
  }
}

基本原理解析如下:

1.经过ConfigurableEnvironment取出一切的PropertySource并依次遍历

2.过滤掉不符合咱们要求的PropertySource,由于PropertySource有许多子类,并不是一切的PropertySource实例都符合咱们包装的要求;

3.对符合要求的PropertySource做一层包装,其实便是静态代理;

4.用包装好的PropertySource替换掉之前的PropertySource实例;

经过上述一系列的操作,咱们就能够在PropertySource取值的时分做一些自定义的操作了,比如针对密文暗码进行解密;

剩余的另一个问题便是加解密的问题,暗码学里边有对称加密非对称加密,这两种加密方法的差异便是对称加密的加密解密都需求同一个密钥,而非对称加密加密的时分需求公钥,解密的时分需求私钥;

了解了对称加密非对称加密的差异,假如咱们使用的是对称加密,那么一定要防止密文和密钥放在同一个当地;非对称加密一定要防止密文和私钥放在同一个当地;

东西介绍

接下来咱们要介绍一款专门针对这个需求的jar东西,它便是jasypt,咱们能够去maven库房找到相关的包:

   <dependency>
      <groupId>com.github.ulisesbocchio</groupId>
      <artifactId>jasypt-spring-boot-starter</artifactId>
      <version>3.0.5</version>
    </dependency>

它的完成原理其实便是咱们上面所讲述的,经过自定义BeanFactoryPostProcessorConfigurableEnvironment中的PropertySource实例进行阻拦包装,在包装类的完成上做一层解密操作,这样就完成了对密文暗码的解密;

导入上述依靠后,该东西就已经主动收效了,咱们就能够修正对应的装备了,首先咱们先针对该东西做一些装备:

jasypt:
  encryptor:
  # 密钥
   password: ""
   property:
   # 密文前缀
    prefix: ""
   # 密文后缀
    suffix: ""

在上述装备中,jasypt.encryptor.password是一定要装备的,这便是加解密的密钥,默许的加密算法是PBEWITHHMACSHA512ANDAES_256;另外jasypt.encryptor.property.prefixjasypt.encryptor.property.suffix分别是密文前缀和密文后缀,是用来标注需求解密的密文的,假如不装备,默许的密文前缀是ENC(,密文后缀是);默许状况下,咱们的密文如下所示:

spring:
  datasource:
   password: "ENC(DzANBAhBWXxZqAOsagIBCoaw8FV4gYRbid7G70UEM24=)"

还有一个需求注意的点便是jasypt.encryptor.password不能与密文放在一同,咱们能够在项目当中经过体系属性、命令行参数或环境变量传递;

完成自定义加解密

假如jasypt供给的加解密方法不能满意咱们的项目需求,咱们还能够自己完成加解密:


@Bean("jasyptStringEncryptor")
  public StringEncryptor jasyptStringEncryptor(){
    return new StringEncryptor() {
      @Override
      public String encrypt(String s) {
    // TODO 加密
        return null;
      }
​
      @Override
      public String decrypt(String s) {
    // TODO 解密
        return null;
      }
    };
  }

注意咱们的BeanName,默许状况下一定要设置成jasyptStringEncryptor,不然不会收效,假如想要改变这个BeanName,也能够经过修正这个装备参数来自定义StringEncryptor实例所对应的BeanName

jasypt:
  encryptor:
  # 自定义StringEncryptor的BeanName
   bean: ""

如何生成密文

生成密文的这个操作仍是要自个儿经过调用StringEncryptor实例来加密生成,能够参考以下代码:

@Component
public class StringEncryptorUtil{
 @Autowired
 private StringEncryptor encryptor;
 
 public void encrypt(){
  String result = encryptor.encrypt("123456");
  System.out.println(result);
  }
}

毕竟需求加密的操作只需求在项目生命周期中执行一次,所以咱们只需求简略地写一个东西类调用一下即可;

本文正在参加「金石方案」