likes
comments
collection

通过mybatis自定义参数类型转换器,进行数据库字段加密脱敏

作者站长头像
站长
· 阅读数 22

通过mybatis自定义参数类型转换器,进行数据库字段加密脱敏

1 问题背景

在数据库存储人员的信息时,有一些信息是敏感数据,如身份证号、出生地等。为了防止信息泄漏,这些信息不允许直接在数据库中查看,此时就需要对这些字段进行加密存储,但在页面查看的仍旧是解密后的数据。这里就涉及到加解密的问题,有两种解决方案。

2 解决方案

2.1 使用数据库加密算法

通过数据库自带的加密算法,在sql里直接使用算法加解密,这种有较多缺点,不推荐使用。

  • 写法繁琐,不易开发维护

  • 适配多种数据库时,算法难以统一,工作量也剧增

2.2 使用mybatis的自定义参数类型转换器

    通过自定义Java类型和类型转换器,并在mapper.xml里标注类型,通过mybatis进行加解密。

  • 加密算法可自由选择

  • 开发维护比较简单方便

  • 不存在适配多种数据库的问题

3 一般web项目使用

3.1 创建自定义Java类型

创建一个空白类,在mapper.xml文件查询和新增修改时标注说明,表示需要mybatis进行相关类型转换。

@Alias("SecretField")
public class SecretField {
}

3.2 自定义类的转换处理器

这里使用国密SM4进行加解密,当然算法可自由选择别的如AES等。

@MappedTypes注解映射刚才的自定义Java类

@MappedTypes(SecretField.class)
public class SecretFieldTypeHandler extends BaseTypeHandler<String> {

    private static final Logger log = LoggerFactory.getLogger(SecretFieldTypeHandler.class);

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        try {
            if (StringUtils.hasText(parameter)) {
                String encryptStr = Sm4Util.encryptData_ECB(parameter);
                ps.setString(i, encryptStr);
            }
        } catch (Exception e) {
            ps.setString(i, parameter);
            log.error("mybatis加密参数异常,i:{},parameter:{}", i, parameter);
        }
    }

    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String columnValue = rs.getString(columnName);
        try {
            if (StringUtils.hasText(columnValue)) {
                columnValue = Sm4Util.decryptData_ECB(columnValue);
            }
        } catch (Exception e) {
            log.error("mybatis解密参数异常,columnName:{}, columnValue:{}", columnName, columnValue);
        }
        return columnValue;
    }

    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return null;
    }

    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return null;
    }
}

3.3 配置自定义类型和类型转换器

这一步也可使用相关注解完成。

配置文件为mybatis的配置文件,如mybatis-config.xml。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE configuration PUBLIC "-//mybaties.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
    </settings>

    <typeAliases>
            <typeAlias type="com.banxian.mybatis.alias.SecretField" alias="SecretField"/>
    </typeAliases>
    <typeHandlers>
        <typeHandler handler="com.banxian.mybatis.typehandler.SecretFieldTypeHandler" />
    </typeHandlers>

</configuration>

3.4 查询使用

查询返回需通过resultMap映射,在加密字段进行javaType指明需要的类型转换器

另外如果旧代码中结果集大量直接使用map返回,时间紧张的情况下,则可考虑写一个工具类,在service里调用进行手动解密,没得办法呀!。

    <resultMap id="BaseResultMap" type="User">
        <result column="sfz" property="sfz"  javaType="SecretField"/>
    </resultMap>

3.5 新增修改使用

在设置相关参数值时,也就是在#{}里,使用javaType指明需要的类型转换器

   insert into ... values(...
    <if test="sfz!= null">
        #{sfz,jdbcType=VARCHAR,javaType=SecretField},
    </if>
   )

4. springboot项目使用

  • 自定义类型和类型转换器代码和上面一样,这里不在赘述。

  • 项目使用了mybatis-plus插件。

4.1 配置自定义类型和类型转换器

在项目配置文件里application.yml里加上配置type-handlers-package 和   type-aliases-package:分别指出你的类型转换器所在包名和自定义类所在包名。

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted
      logic-not-delete-value: "0"
      logic-delete-value: "1"
  mapper-locations: "classpath*:/mapper/**/*.xml"
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  type-handlers-package: com.banxian.mybatis.typehandler
  type-aliases-package: com.banxian.mybatis.alias

4.2 mybatis-plus的使用

当然上面web项目的查询、新增和修改使用方法这里也是可以正常使用的

1)在实体类上使用注解 @TableName(autoResultMap = true)

2)在加密字段上指明类型@TableField(value = "sfz", typeHandler = SecretFieldTypeHandler.class)

@Data
@TableName(autoResultMap = true)
public class User implements Serializable {

    private static final long serialVersionUID = 4344848828462926573L;

    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Integer id;

    /**
     * 名称
     */
    private String userName;

    /**
     * 是否删除 1:是  0:否
     */
    @TableLogic
    private String deleted;

    /**
     * 是否有效 1:是  0:否
     */
    private String valid;

    /**
     * 创建时间
     */
    @TableField(value = "create_at", fill = FieldFill.INSERT)
    private LocalDateTime createAt;

    /**
     * 更新时间
     */
    @TableField(value = "update_at", fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateAt;

    @TableField(value = "sfz", typeHandler = SecretFieldTypeHandler.class)
    private String sfz;
}