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

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更新不能加密,需要将数据加密后更新。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值