Jasypt加密SpringBoot配置文件和自动加密数据库敏感信息

发布时间 2023-06-01 22:58:18作者: shigp1

Jasypt是开源的加密和解密的组件。和Spring提供了很好的集成。

一、加密SpringBoot配置文件

 
新建SpringBoot项目,添加依赖

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

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

增加Jasypt配置类JasyptConfig

@Configuration
public class JasyptConfig {

    /**
     * 配置StandardPBEStringEncryptor加解密器的配置
     * @return
     */
    @Bean
    public EnvironmentStringPBEConfig environmentStringPBEConfig() {
        EnvironmentStringPBEConfig environmentStringPBEConfig = new EnvironmentStringPBEConfig();
        /**
         * 设置算法
         */
        environmentStringPBEConfig.setAlgorithm("PBEWithMD5AndDES");
        /**
         * 从JVM环境变量中读取APP_ENCRYPTION_PASSWORD的值作为密码
         */
        environmentStringPBEConfig.setPasswordSysPropertyName("APP_ENCRYPTION_PASSWORD");
        return environmentStringPBEConfig;
    }

    /**
     * 配置StandardPBEStringEncryptor加解密器
     * @return
     */
    @Bean("jasyptStringEncryptor")
   public StandardPBEStringEncryptor standardPBEStringEncryptor() {
       StandardPBEStringEncryptor standardPBEStringEncryptor = new StandardPBEStringEncryptor();
       standardPBEStringEncryptor.setConfig(environmentStringPBEConfig());
       return standardPBEStringEncryptor;
   }


}

注意:jasyptStringEncryptorbean名字不能修改。StandardPBEStringEncryptor的密码通过配置EnvironmentStringPBEConfig从JVM环境变量APP_ENCRYPTION_PASSWORD获取。
 
application.properties配置端口:

server.port=8500

在Controller中增加:

 @Autowired
private StringEncryptor jasyptStringEncryptor;

 @RequestMapping("/encrypt")
public String encrypt(String str) {
    if (str != null) {
        return jasyptStringEncryptor.encrypt(str);
    } else {
        return null;
    }
}

用于返回加密字符串。str是明文。在启动类的JVM参数中添加-DAPP_ENCRYPTION_PASSWORD=dffg438765,等号后面的值自己修改。启动后访问http://localhost:8500/encrypt?str=123,复制得到的结果。
 

application.properties配置:

my.value=ENC(aBGbIXKfNcOPcdB1FQbNVg==)

括号里面的值是上面的加密字符串。在Controller中增加:

 @Value("${my.value}")
private String myValue;

@RequestMapping("/myValue")
public String myValue() {
    return myValue;
}

重启后访问http://localhost:8500/myValue,看到123。
 

application.properties中的ENC是否可以修改呢?Jasypt配置由JasyptEncryptorConfigurationProperties配置,看下JasyptEncryptorConfigurationProperties类:

private String bean = "jasyptStringEncryptor";

@NestedConfigurationProperty
private PropertyConfigurationProperties property;

这里只展示两个字段。jasyptStringEncryptor是加密和解密配置文件中的加密数据使用的Bean名。再看PropertyConfigurationProperties类:

    private String detectorBean = "encryptablePropertyDetector";
    private String resolverBean = "encryptablePropertyResolver";
    private String filterBean = "encryptablePropertyFilter";
    private String prefix = "ENC(";
    private String suffix = ")";

可以看到前缀是ENC(,后缀是)。所以要修改ENC前缀可在application.properties增加配置:

my.value=my_enc(aBGbIXKfNcOPcdB1FQbNVg==)

jasypt.encryptor".property.prefix=my_enc(

重启后查看效果。

二、自动加密数据库敏感字段

数据库表中保存的敏感字段,比如姓名,证件号码,手机号等。如果明文展示或泄露就会导致数据泄露造成麻烦。可以在保存时,查询时加密和解密字段。这里用Postgresql数据库做演示。用MybatisPlus操作数据库,也可以用Mybatis

 

添加依赖:

 <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
    </dependency>

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.2</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>

application.properties增加数据库配置和mybatisPlus的mapper的sql文件位置:

spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=my_enc(8uct/atZocNDwefc0VoS+DJO7Xt7a4ss)

mybatis-plus.mapper-locations=classpath*:mapper/*.xml

在启动类添加:

@MapperScan("com.example.mytest.mapper")

mybatisPlus扫描Mapper接口包路径。

添加实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@TableName("t_user")
public class UserEntity {
    /**
     * 主键
     */
    @TableId(type=IdType.AUTO)
    private Long idKey;

    /**
     * 姓名
     */
    private String name;

    /**
     * 手机号
     */
    private String phone;
}

表名是t_user,主键是idKey,自动生成。同时使用了lombok注解。

 

增加Mapper接口:

public interface UserMapper extends BaseMapper<UserEntity> {
}

在数据库新增表:

create table t_user (
  id_key bigserial primary key,
  name varchar(50),
  phone varchar(100)
);

bigserial相当于Oracle的序列,可以自动生成。

加密和解密数据库敏感字段是使用Mybatisplugins。MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

Executor是执行sql时,ParameterHandler是处理参数,ResultSetHandler是映射结果,StatementHandler是负责处理MyBatis与JDBC之间Statement的交互。通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截方法签名即可。要自动加解密数据敏感字段,只需要拦截ParameterHandlerResultSetHandler即可。
 

新增注解EnCryptData

/**
 * 要加密和解密的类加这个注解
 */
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnCryptData {
}

要自动加密和解密的类上加这个注解。

 

新增注解EnCryptField:

/**
 * 要加密和解密的字段加这个注解
 */
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnCryptField {
}

在自动加密和解密的字段加这个注解。
 

保存或更新时自动加密数据,要拦截ParameterHandler,增加自动加密拦截器EnCrpytPlugin

@Intercepts({@Signature(
  type= ParameterHandler.class,
  method = "setParameters",
  args = PreparedStatement.class)})
@Component
@Slf4j
public class EnCrpytPlugin implements Interceptor {

  @Autowired
  private StringEncryptor jasyptStringEncryptor;

  public Object intercept(Invocation invocation) throws Throwable {

    ParameterHandler parameterHandler = (ParameterHandler)invocation.getTarget();

    Object parameterObject = parameterHandler.getParameterObject();

    if (parameterObject != null) {
      Class<?> aClass = parameterObject.getClass();
      log.info("parameterHandler:{}",aClass);

      EnCryptData enCryptData = aClass.getAnnotation(EnCryptData.class);
      log.info("enCryptData:{}",enCryptData);
      if (enCryptData != null) {
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field field : declaredFields) {
          EnCryptField enCryptField = field.getAnnotation(EnCryptField.class);
          if (enCryptField != null) {
            field.setAccessible(true);
            Object o = field.get(parameterObject);
            log.info("field:{},o:{}",field.getName(), o);
            /**
             * 暂时只支持String类型加密
             */
            field.set(parameterObject, o != null ? jasyptStringEncryptor.encrypt(o.toString()) : null);
          }
        }
      }
    }
    return invocation.proceed();
  }
}

思路就是设置参数时将参数类上有@EnCryptData同时字段有@EnCryptField将加密后的值替换原来的值。将EnCrpytPlugin加入Spring管理即可。

 

修改UserEntity:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@TableName("t_user")
@EnCryptData
public class UserEntity {
    /**
     * 主键
     */
    @TableId(type=IdType.AUTO)
    private Long idKey;

    /**
     * 姓名
     */
    private String name;

    /**
     * 手机号
     */
    @EnCryptField
    private String phone;
}

修改Controller:

 @Autowired
private UserMapper userMapper;

@RequestMapping("/addUser")
public String addUser() {
    UserEntity user = UserEntity.builder()
            .name("张三")
            .phone("13100000000")
            .build();

    userMapper.insert(user);

    return "success";
}


@RequestMapping("/listUser")
public List<UserEntity> listUser() {
    return userMapper.selectList(Wrappers.lambdaQuery(UserEntity.class));
}

addUser用于新增用户,listUser用于查询所有用户。在浏览器先执行http://localhost:8500/addUser,在执行http://localhost:8500/listUser,看到:

[{"idKey":1,"name":"张三","phone":"xN8bEY4pndmLd2nV5mZYf5RI9IbdqKYC"}]

可知phone已经自动加密。

 

现在增加自动解密插件:

@Intercepts({@Signature(
  type= ResultSetHandler.class,
  method = "handleResultSets",
  args = Statement.class)})
@Component
@Slf4j
public class DeCrpytPlugin implements Interceptor {

  @Autowired
  private StringEncryptor jasyptStringEncryptor;

  public Object intercept(Invocation invocation) throws Throwable {

    Object resultObject = invocation.proceed();

    if (resultObject == null) {
      return null;
    }

    log.info("返回对象类型:{}", resultObject.getClass());

    if (resultObject instanceof List) {
      List list = (List) resultObject;
      if (list.isEmpty()) {
        return list;
      }

      /**
       * 获取List元素Class
       */
      Class<?> aClass = list.get(0).getClass();

      EnCryptData enCryptData = aClass.getAnnotation(EnCryptData.class);

      if (enCryptData != null) {
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Object o : list) {
          for (Field field : declaredFields) {
            EnCryptField enCryptField = field.getAnnotation(EnCryptField.class);
            if (enCryptField != null) {
              field.setAccessible(true);
              Object o1 = field.get(o);
              log.info("field:{},o1:{}",field.getName(), o1);
              field.set(o, o1 != null ? jasyptStringEncryptor.decrypt(o1.toString()) : null);
            }
          }
        }
      }
    }


    return resultObject;
  }
}

思路和EnCrpytPlugin。区别是在映射结果时将解密数据替换原来的值。访问http://localhost:8500/listUser,看到:

[{"idKey":2,"name":"张三","phone":"13100000000"}]

数据已经自动解密。

 
 
 
 
 

参考:https://mybatis.net.cn/configuration.html#plugins