【使用mybatis的BaseTypeHandler来给敏感字段进行AES加密】

概要

使用mybatis的BaseTypeHandler来给敏感字段进行AES加密

整体架构流程

开发中,公司会要求对敏感字段进行加密处理后入库,查询的时候需要解密后展示,这就要求原字符串和加密字符串之间进行转换

如果一两个字符串还好,可以直接在入库的时候和查询的时候处理一下,但是如果有很多字段都需要这样处理怎么办?

Mybatis里面有一个TypeHandler可以解决这个问题,只需要在需要加密/解密的字段在xml上映射自定义的TypeHandler,查询返回必须是resultMap,入库就是在xml入库的字段上加上TypeHandler,
自己自定义加密工具,可以根据你的需求来定义,因为我项目中涉及的字段比较多,其中涉及一些复杂的sql,TypeHandler不能保证都能使用,所以我加解密的是一种十六进制的字符,跟MySQL原生的加解密函数HEX(AES_ENCRYPT(str,key_str))函数生成加密串就行,和工具AESUtil生成的加密串是一致的。mysql中解密AES字符串AES_DECRYPT(UNHEX(str),key_str)

技术细节

为了方便,这里我们使用的是Hutool的AES加密工具

<dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.5.8</version>
        </dependency>

然后创建AesUtil加密工具, private static String keyStr = “自定义的key”; 这个key加密和解密要用同一个,不然会解密失败

import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.SecureUtil;
import org.apache.commons.lang3.StringUtils;

public class AESUtil {
    private static final String AES_KEY = "AES_KEY";

    private static final byte[] KEY_BYTES;

    private static String keyStr = "自定义的key";

    static {
        KEY_BYTES = new byte[16];
        int i = 0;
        for (byte b : keyStr.getBytes()) {
            KEY_BYTES[i++ % 16] ^= b;
        }
    }

    public static String encrypt(String content) {
        if (StringUtils.isBlank(content)) {
            return content;
        }
        return HexUtil.encodeHexStr(SecureUtil.aes(KEY_BYTES).encrypt(content), false);
    }

    public static String decrypt(String content) {
        if (StringUtils.isBlank(content)) {
            return content;
        }
        return SecureUtil.aes(KEY_BYTES).decryptStr(content);
    }

    public static void main(String[] args) {
        String encrypted = encrypt("测试");
        System.out.println(encrypted);
        System.out.println(decrypt(encrypted));
    }
}

定义AesTypeHandler


import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * 继承Mybatis的 BaseTypeHandler类,重写方法
 */

public class TypeHandler extends BaseTypeHandler<String> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {

        ps.setString(i, AESUtil.encrypt(parameter));
    }

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

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

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

准备一个实体Test,我要加密phone和name字段

import lombok.Data;

@Data
public class Test {

    private Integer id;
    private String phone;
    private String name;
}

对应的xml里的操作

<resultMap id="BaseResultMap" type="cn.com.fl.service.common.entity.Test">
    <!--@mbg.generated-->
    <!--@Table test-->
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="phone" jdbcType="VARCHAR" property="phone" typeHandler="cn.com.fl.service.common.AesTypeHandler.TypeHandler"/>
    <result column="name" jdbcType="VARCHAR" property="name" typeHandler="cn.com.fl.service.common.AesTypeHandler.TypeHandler"/>
  </resultMap>
  <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    <!--@mbg.generated-->
    <!--select
    id,CAST(AES_DECRYPT(UNHEX(phone),'scyd')as char) as phone,name
    from test
    where id = #{id,jdbcType=INTEGER}-->

    select
    id,phone,`name`
    from test
    where id = #{id,jdbcType=INTEGER}
  </select>

查询不用做任何操作,resultMap在加有TypeHandler的字段是自动映射进行解密操作

  <insert id="insert" keyColumn="id" keyProperty="id" parameterType="cn.com.fl.service.common.entity.Test" useGeneratedKeys="true">
    INSERT into test (phone, `name`)
    values (#{phone,jdbcType=VARCHAR,typeHandler=cn.com.fl.service.common.AesTypeHandler.TypeHandler}, #{name,jdbcType=VARCHAR,typeHandler=cn.com.fl.service.common.AesTypeHandler.TypeHandler})
  </insert>

service层Dao层省略…

测试

测试代码

   @ResponseBody
    @PostMapping("/test")
    public Result add(){
        Test test = new Test();
        test.setPhone("15999999999");
        test.setName("测试");
        int insert = mapper.insert(test);
        if (insert>0){
            return new Result().success();
        }
        return new Result().error("插入失败");
    }

postman请求一下

可以看到控制台是打印出来了加密的效果了

在这里插入图片描述

数据库效果

在这里插入图片描述

然后我们来查询一下这条数据看看

  @ResponseBody
    @RequestMapping("/select")
    public String select(){
       Test test = mapper.selectByPrimaryKey(24);

        return test.toString() ;
    }

在这里插入图片描述

我们可以试试用mysql原生的函数AES_DECRYPT(UNHEX(str),key_str)能否解密

在这里插入图片描述
答案是可以解密的,测试成功!!!

小结

因为我项目中使用的是mybatis没有用mybatis plus 在xml动态映射的时候显的有点僵硬,如果是用mybatis plus的可以用注解的方式,只需要在需要加密/解密的字段上使用@TableField(typeHandler = AesTypeHandler.class),

包含该字段的实体上使用@TableName(autoResultMap = true)即可(PS:AesTypeHandler是自定义的TypeHandler),大家有兴趣的可以去试试

参考链接

https://www.cnblogs.com/java-spring/p/14676670.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值