AES 配合mybaties 实现指定字段自动加解密

2023-11-13

1 加密工具类

@Slf4j
public class AESUtil {


    /**
     * 密钥长度: 128, 192 or 256
     */
    private static final int KEY_SIZE = 256;

    /**
     * 加密/解密算法名称
     */
    private static final String ALGORITHM = "AES";

    /**
     * 随机数生成器(RNG)算法名称
     */
    private static final String RNG_ALGORITHM = "SHA1PRNG";

  /**
    * 生成密钥的种子不可泄露
    */
    public static final String KEY = "xxxxxxxxxxxx";


    /**
     * 生成密钥对象
     */
    private static SecretKey generateKey(byte[] key) throws Exception {
        // 创建安全随机数生成器
        SecureRandom random = SecureRandom.getInstance(RNG_ALGORITHM);
        // 设置 密钥key的字节数组 作为安全随机数生成器的种子
        random.setSeed(key);

        // 创建 AES算法生成器
        KeyGenerator gen = KeyGenerator.getInstance(ALGORITHM);
        // 初始化算法生成器
        gen.init(KEY_SIZE, random);

        // 生成 AES密钥对象, 也可以直接创建密钥对象: return new SecretKeySpec(key, ALGORITHM);
        return gen.generateKey();
    }

    /**
     * 数据加密: 明文 -> 密文
     */
    public static String encrypt(String content, byte[] key) {
        // 生成密钥对象
        SecretKey secKey;
        try {
            secKey = generateKey(key);
            // 获取 AES 密码器
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            // 初始化密码器(加密模型)
            cipher.init(Cipher.ENCRYPT_MODE, secKey);
            // 加密数据, 返回密文
            byte[] plainBytes = content.getBytes("utf-8");
            byte[] result = cipher.doFinal(plainBytes);
            return Base64.getEncoder().encodeToString(result);//通过Base64转码返回
        } catch (Exception e) {
            throw new EncrypErrorException(10000, "AES 加密失败:" + e.getMessage());
        }

    }

    /**
     * 数据解密: 密文 -> 明文
     */
    public static String decrypt(String content, byte[] key) {
        try {
            // 生成密钥对象
            SecretKey secKey = generateKey(key);

            // 获取 AES 密码器
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            // 初始化密码器(解密模型)
            cipher.init(Cipher.DECRYPT_MODE, secKey);

            // 解密数据, 返回明文
            byte[] result = cipher.doFinal(Base64.getDecoder().decode(content));
            return new String(result, "utf-8");
        } catch (Exception e) {
            throw new EncrypErrorException(10001, "AES 解密失败:" + e.getMessage());
        }
    }

    /**
     * 加密文件: 明文输入 -> 密文输出
     */
    public static void encryptFile(File plainIn, File cipherOut, byte[] key) throws Exception {
        aesFile(plainIn, cipherOut, key, true);
    }

    /**
     * 解密文件: 密文输入 -> 明文输出
     */
    public static void decryptFile(File cipherIn, File plainOut, byte[] key) throws Exception {
        aesFile(plainOut, cipherIn, key, false);
    }

    /**
     * AES 加密/解密文件
     */
    private static void aesFile(File plainFile, File cipherFile, byte[] key, boolean isEncrypt) throws Exception {
        // 获取 AES 密码器
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        // 生成密钥对象
        SecretKey secKey = generateKey(key);
        // 初始化密码器
        cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, secKey);

        // 加密/解密数据
        InputStream in = null;
        OutputStream out = null;

        try {
            if (isEncrypt) {
                // 加密: 明文文件为输入, 密文文件为输出
                in = new FileInputStream(plainFile);
                out = new FileOutputStream(cipherFile);
            } else {
                // 解密: 密文文件为输入, 明文文件为输出
                in = new FileInputStream(cipherFile);
                out = new FileOutputStream(plainFile);
            }

            byte[] buf = new byte[1024];
            int len = -1;

            // 循环读取数据 加密/解密
            while ((len = in.read(buf)) != -1) {
                out.write(cipher.update(buf, 0, len));
            }
            out.write(cipher.doFinal());    // 最后需要收尾

            out.flush();

        } finally {
            close(in);
            close(out);
        }
    }

    private static void close(Closeable c) {
        if (c != null) {
            try {
                c.close();
            } catch (IOException e) {
                // nothing
            }
        }
    }
}

测试:

   public static void main(String[] args) {
        String content = "[{\"code\":10101,\"name\":\"基本工资\",\"value\":\"5120.1\"},{\"code\":10102,\"name\":\"岗位工资\",\"value\":\"2000.2\"},{\"code\":10103,\"name\":\"职务工资\",\"value\":\"222.01\"}]";
        System.out.println("原文:" + content);
        String encrypt = AESUtil.encrypt(content, AESUtil.KEY.getBytes());
        System.out.println("密文:" + encrypt + "  长度:" + encrypt.length());
        String decrypt = AESUtil.decrypt(encrypt, AESUtil.KEY.getBytes());
        System.out.println("解密后原文:" +decrypt);
        System.out.println(decrypt.equals(content));
    }

结果:
在这里插入图片描述

2.mybaties参数,结果处理器

通过使用MyBatis的typeHandler功能,对入参和出参进行处理,实现无缝加密解密(将明文加密后保存至数据库;从数据库读取时,自动将密文解密成明文)

public class AESEncryptHandler extends BaseTypeHandler<Object> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
        String content = (parameter == null) ? "" : parameter.toString();
        ps.setString(i, AESUtil.encrypt(content, AESUtil.KEY.getBytes()));
    }

    @Override
    public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String columnValue = rs.getString(columnName);
        return AESUtil.decrypt(columnValue,  AESUtil.KEY.getBytes());
    }

    @Override
    public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException{
        String columnValue = rs.getString(columnIndex);
        return AESUtil.decrypt(columnValue,  AESUtil.KEY.getBytes());
    }

    @Override
    public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException{
        String columnValue = cs.getString(columnIndex);
        return AESUtil.decrypt(columnValue,  AESUtil.KEY.getBytes());
    }

}

3.实体属性配置:

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName(value = "salary_change_record",autoResultMap = true)
@ApiModel(value="HrmSalaryChangeRecord对象", description="定薪调薪记录表")
public class HrmSalaryChangeRecord extends BaseEntity<HrmSalaryChangeRecord> implements Serializable{

    private static final long serialVersionUID=1L;

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

    @ApiModelProperty(value = "员工id")
    private Integer employeeId;

    @ApiModelProperty(value = "记录类型 1 定薪 2 调薪")
    private Integer recordType;

    @ApiModelProperty(value = "调薪原因 0 入职定薪 1 入职核定 2 转正 3 晋升 4 调动 5 年中调薪 6 年度调薪 7 特别调薪 8 其他")
    private Integer changeReason;

    @ApiModelProperty(value = "生效时间")
    @DateTimeFormat(pattern="yyyy-MM-dd")
    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd")
    private Date enableDate;

    @ApiModelProperty(value = "试用期调整前工资")
    @TableField(typeHandler = AESEncryptHandler.class)
    private String proBeforeSum;

    @ApiModelProperty(value = "试用期调整后工资")
    @TableField(typeHandler = AESEncryptHandler.class)
    private String proAfterSum;

    @ApiModelProperty(value = "试用期工资明细")
    @TableField(typeHandler = AESEncryptHandler.class)
    private String proSalary;

    @ApiModelProperty(value = "正式调整前工资 json")
    @TableField(typeHandler = AESEncryptHandler.class)
    private String beforeSum;

    @ApiModelProperty(value = "正式调整后工资")
    @TableField(typeHandler = AESEncryptHandler.class)
    private String afterSum;

    @ApiModelProperty(value = "正式工资明细 json")
    @TableField(typeHandler = AESEncryptHandler.class)
    private String salary;

    @ApiModelProperty(value = "状态 0 未生效 1 已生效 2 已取消")
    private Integer status;

    @ApiModelProperty("员工状态")
    private Integer employeeStatus;

    @ApiModelProperty(value = "备注")
    private String remarks;

    @ApiModelProperty(value = "调整前总工资")
    @TableField(typeHandler = AESEncryptHandler.class)
    private String beforeTotal;

    @ApiModelProperty(value = "调整后总工资")
    @TableField(typeHandler = AESEncryptHandler.class)
    private String afterTotal;

}

要开启autoResultMap 自动配置ResultMap

使用@TableField注解标识属性,并通过typeHandler 指定我们自定义的mybaties 参数结果处理器。
@TableField(typeHandler = AESEncryptHandler.class)

4 demo

4.1 测试mybaties-plus自带方法的新增:

   @Test
    public void testInsert(){
        HrmSalaryChangeRecord salaryChangeRecord=new HrmSalaryChangeRecord();
        salaryChangeRecord.setAfterTotal("25343.31").setSalary("{\"newSalary\":[{\"code\":10101,\"name\":\"基本工资\",\"value\":\"5120.1\"},{\"code\":10102,\"name\":\"岗位工资\",\"value\":\"20001.2\"},{\"code\":10103,\"name\":\"职务工资\",\"value\":\"222.01\"}],\"oldSalary\":[{\"code\":10101,\"name\":\"基本工资\",\"value\":\"5120.1\"},{\"code\":10102,\"name\":\"岗位工资\",\"value\":\"2000.2\"},{\"code\":10103,\"name\":\"职务工资\",\"value\":\"222.01\"}]}")
                .setBeforeTotal("7342.31").setEmployeeId(2).setRecordType(2).setChangeReason(1).setEnableDate(new Date())
                .setProBeforeSum("0").setProSalary("{\"newSalary\":[{\"code\":10101,\"name\":\"基本工资\",\"value\":\"0\"},{\"code\":10102,\"name\":\"岗位工资\",\"value\":\"0\"},{\"code\":10103,\"name\":\"职务工资\",\"value\":\"0\"}],\"oldSalary\":[{\"code\":10101,\"name\":\"基本工资\",\"value\":\"0\"},{\"code\":10102,\"name\":\"岗位工资\",\"value\":\"0\"},{\"code\":10103,\"name\":\"职务工资\",\"value\":\"0\"}]}")
               .setProAfterSum("0").setBeforeSum("0")
                .setAfterSum("7342.31").setStatus(1).setEmployeeStatus(1).setRemarks("123");
        salaryChangeServer.insertRecord(salaryChangeRecord);
    }

执行sql:
在这里插入图片描述

数据库结果:
可见数据已经加密
在这里插入图片描述

4.2测试mybaties-plus自带方法查询:

  @Test
    public void testList(){
        List<HrmSalaryChangeRecord> list = salaryChangeServer.list();
       list.forEach(System.out::println);
    }

查询结果:

查询结果成功解密。
在这里插入图片描述

4.3 xml中自定义sql返回实体类型

xml:

    <!--无法解密-->
    <select id="selectByEmployeeId2" resultType="HrmSalaryChangeRecord">
        select *
        from salary_change_record
        where employee_id = #{employeeId}
    </select>

mapper:

HrmSalaryChangeRecord selectByEmployeeId2(Integer employeeId);

测试

 @Test
    public void TestSelectByEmployeeId2(){
        System.out.println(hrmSalaryChangeMapper.selectByEmployeeId2(2));
    }

结果:

查询结果没有解密,在xml中自定义sql返回类型为实体类时无法触发TypeHandle。
在这里插入图片描述

4.4 xml中自定义sql返回resultMap 在map中配置typeHandler

xml:

<resultMap id="salaryChangeRecord" type="HrmSalaryChangeRecord">
 <id property="id" column="id"/>
 <result property="employeeId" column="employee_id"/>
 <result property="recordType" column="record_type"/>
 <result property="changeReason" column="change_reason"/>
 <result property="proBeforeSum" column="pro_before_sum" typeHandler="xxx.AESEncryptHandler"/>
.....省略剩下的map配置

 <!--resultMap 中配置typeHandler 可以实现解密-->
    <select id="selectByEmployeeId" resultMap="salaryChangeRecord">
        select *
        from salary_change_record
        where employee_id = #{employeeId}
    </select>

测试:

@Test
    public void TestSelectByEmployeeId(){
        System.out.println(hrmSalaryChangeMapper.selectByEmployeeId(2));
    }

测试结果:

成功解密
在这里插入图片描述

4.5 mybaties-plus 自带Wrappers查询

测试:

 @Test
    public void TestSelectByEmployeeId3(){
        System.out.println(salaryChangeServer.list(Wrappers.lambdaQuery(HrmSalaryChangeRecord.class).eq(HrmSalaryChangeRecord::getEmployeeId,2)
                .select(HrmSalaryChangeRecord::getSalary,HrmSalaryChangeRecord::getBeforeSum)));
    }

执行结果:

查询结果可以解密
在这里插入图片描述

4.5 mybaties-plus 自带方式更新

测试:

    @Test
    public void testUpdatae()
    {
        HrmSalaryChangeRecord salaryChangeRecord = new HrmSalaryChangeRecord();
        salaryChangeRecord.setBeforeTotal("500");
        salaryChangeRecord.setBeforeSum("1024");
        salaryChangeRecord.setSalary("1000000000000");
        salaryChangeRecord.setId(9);
        salaryChangeServer.updateById(salaryChangeRecord);
        System.out.println(hrmSalaryChangeMapper.selectByEmployeeId(2));
    }

结果:

更新成功。
在这里插入图片描述

数据库结果:

数据库也还是密文状态
在这里插入图片描述

4.5 mybaties-plus 自带Wrappers方式更新

测试:

   @Test
    public void testUpdatae2()
    {
        salaryChangeServer.update(Wrappers.lambdaUpdate(HrmSalaryChangeRecord.class)
                        .eq(HrmSalaryChangeRecord::getEmployeeId,2).set(HrmSalaryChangeRecord::getSalary,"20000000000")
                .set(HrmSalaryChangeRecord::getBeforeSum,"2048"));
    }

数据库结果:

数据库成功更新但是是明文,Wrappers更新无法使用typeHandel。
在这里插入图片描述

解决办法:

在 set值时使用AESUtil.encrypt加密数据。

 @Test
    public void testUpdatae3()
    {
        salaryChangeServer.update(Wrappers.lambdaUpdate(HrmSalaryChangeRecord.class)
                .eq(HrmSalaryChangeRecord::getEmployeeId,2).set(HrmSalaryChangeRecord::getSalary, AESUtil.encrypt("500000",AESUtil.KEY.getBytes()))
                .set(HrmSalaryChangeRecord::getBeforeSum,AESUtil.encrypt("3000",AESUtil.KEY.getBytes())));

        System.out.println(hrmSalaryChangeMapper.selectByEmployeeId(2));
    }

执行结果:
在这里插入图片描述

数据库数据:

还是密文说明加密成功。
在这里插入图片描述

5.结论

5.1新增

1)mybaties-plus自带的新增可以加密

5.2查询:

1)mybaties-plus自带的查询方法和wrappers查询都可以解密
2)xml中自定义sql 返回实体类不能解密,返回resultMap在map中配置typeHandler可以解密。

5.3更新

1)mybaties-plus自带的更新方法可以加密,
2)自带的wrappers更新不能加密,需要将数据加密后更新。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

AES 配合mybaties 实现指定字段自动加解密 的相关文章

随机推荐

  • kettle表数据比较

    使用合并记录组件 我的kettle死活不能保存中文 唉 其中tab in 1和tab in 2代表两个数据源 合并记录 新旧数据源可随意指定 获取需要对比的字段 此处为了对比将比较记录先放在file中 identical 比较的所有字段相同
  • PPTP - GRE

    PPTP GRE PPTP Point to Point Tunneling Protocol 点对点隧道协议 GRE Generic Routing Encapsulation 通用路由封装 PPTP 的连接过程如下图 PPTP 可以用于
  • Python和Java读写文件的对比

    博主平时用Python比较多 最近因为工作需要使用Java编程 比较之下 发现Python读写文件真是太方便了 Java读写文件非常繁琐 简直让人抓狂 Python读写文件的语句 读文件 with open readFile r as in
  • 小程序实现毛玻璃的效果

    利用css的filter这个属性实现 在有弹框弹出的时候背景出现高斯模糊的效果 写个小例子记录一下 这个是背景是地图的情况下 做的处理 不管是文字还是图片什么的 在弹出框出来的时候给背景添加filter blur 20rpx 中间值的模糊程
  • Linux虚拟机sqlite数据库安装教程、命令实现sqlite

    参考 Linux虚拟机sqlite数据库安装教程 作者 图触靓 发布时间 2021 04 08 19 07 56 网址 https blog csdn net bhbhhyg article details 115528254 一 在官网下
  • Spark性能调优之广播变量

    一 背景 举例来说 虽然是举例 但是基本都是用我们实际在企业中用的生产环境中的配置和经验来说明的 50个executor 1000个task 一个map 10M 默认情况下 1000个task 1000份副本 10G的数据 网络传输 在集群
  • 帆软报表FineReport中数据连接之Tomcat配置JNDI连接

    1 问题描述 在帆软报表FineReport中 通过JNDI方式定义数据连接 首先在Tomcat服务器配置好JNDI 然后在设计器中直接调用JNDI的名字 即可成功使用JNDI连接 连接步骤如下 2 实现步骤 使用版本及环境 下面以Wind
  • 【ES6】Generator函数

    文章目录 一 声明Generator函数 二 调用 三 next 四 yield 五 return与yield区别 一 声明Generator函数 Generator函数 又称生成器函数 是ES6的一个重要的新特性 普通函数用functio
  • 魏副业而战:闲鱼推广显示设备异常怎么办

    我是魏哥 与其在家躺平 不如魏副业而战 今天是三八节 祝各位小姐姐们节日快乐 做网络项目 不免会遇到各种各样的问题 有人勇往直前 找方法 有人选择退缩 不同的选择 不同的结果 那么 遇到问题 我们应该怎么做呢 魏哥建议问度娘 一般情况下 我
  • VMWare Workstation 16 安装 Ubuntu 22.04 LTS

    最近想编译Android8 1 系统源码 不太想安装双系统 先尝试用虚拟机安装Ubuntu来编译试试 过程中也遇到一些特殊的错误 因此做了一次记录 VMWare Workstation 16 的下载和安装这里不作介绍 网上也有很多注册码可用
  • (Animator详解一)mixamo动画导入Unity的一些配置

    Mixamo是Adobe公司出品的免费动画库 可商用 软件分为characters 角色 Animations 动画 两个部分 下方的搜索框可以搜寻你想要的动作动画 网址为 Mixamo 搜索框的子菜单表示动画的类别 当我们的项目需要角色动
  • 【Xilinx Vivado时序分析/约束系列2】FPGA开发时序分析/约束-建立时间

    目录 基本概念 数据结束时间 Data finish time 保持时间门限 保持时间余量 Hold Slack 基本概念 数据结束时间 Data finish time 之前解释了数据达到的时间 对于data arrival time T
  • cpu矿工cpuminer-multi编译与使用

    文章目录 编译步骤 cpuminer multi 矿工运行 cpuminer multi有很多不同前辈开发 这里选用star最多且最流行的 lucasjones cpuminer multi 在编译中遇到了很多坑 这里全部整合到流程中 如果
  • nimg 文件服务器,NIMG-45. DEEP LEARNING-BASED PERITUMORAL MICROSTRUCTURE MAPPING IN GLIOBLASTOMAS USING FR...

    摘要 PURPOSE Characterization of the peritumoral microenvironment is a widely researched but as yet unsolved problem Deter
  • K-近邻算法预测电影类型

    K 近邻算法预测电影类型 k 近邻算法是一种比较简单 但是在一些方面又有很多作用的算法 比较常用的就是推荐入住位置 或者推荐入住酒店等等 K 近邻算法的原理 就是根据特征值 计算出离自己最近的那个分类 自己也属于那个类别 K 近邻是一种分类
  • 吴恩达机器学习(三) 无监督学习

    Unsupervised Learning Unsupervised learning allows us to approach problems with little or no idea what our results shoul
  • Django框架

    目录 目录 一 虚拟环境 1 什么是虚拟环境 2 作用 3 wondows下安装使用 二 Django框架 1 安装Django 2 拓展 虚拟机和虚拟环境问题 2 1虚拟机的三种网络模式 3 创建Django项目 3 1完整创建Djang
  • Python中Print()函数的用法___实例详解(全,例多)

    Python中Print 函数的用法 实例详解 全 例多 目 录 一 print 函数的语法 二 print 打印输出文本 三 print 中空格的使用方法 四 Print 换行 五 区隔符 sep 六 制表符 t 七 输出数学表达式 八
  • Qt:可视化UI设计

    1 创建项目 修改组件的对象名字和显示文本内容 创建一个 Widget Application 项目类 QDialog 在创建窗体时选择基类 QDialog 生成的类命名为 QWDialog 并选择生成窗体 在界面设计时 对需要访问的组件修
  • AES 配合mybaties 实现指定字段自动加解密

    1 加密工具类 Slf4j public class AESUtil 密钥长度 128 192 or 256 private static final int KEY SIZE 256 加密 解密算法名称 private static fi