• MyBatis数据脱敏


    MyBatis数据脱敏

    一、背景

    在我们数据库中有些时候会保存一些用户的敏感信息,比如:手机号、银行卡等信息,如果这些信息以明文的方式保存,那么是不安全的。

    假如:黑客黑进了数据库,或者离职人员导出了数据,那么就可能导致这些敏感数据的泄漏。因此我们就需要找到一种方法来解决这个问题。

    二、解决方案

    由于我们系统中使用了Mybatis作为数据库持久层,因此决定使用Mybatis的TypeHandler或Plugin来解决。

    TypeHandler : 需要我们在某些列上手动指定 typeHandler 来选择使用那个typeHandler或者根据@MappedJdbcTypes@MappedTypes注解来自行推断。

    <result column="phone" property="phone"
    typeHandler="com.llp.llpmybatis.typehandler.EncryptTypeHandler"/>
    
    • 1
    • 2

    Plugin : 可以拦截系统中的 select、insert、update、delete等语句,也能获取到sql执行前的参数和执行后的数据。

    三、需求

    我们有一张用户表tbl_user,里面有客户手机号(phone)和邮箱(email)等字段,其中客户手机号(phone)是需要加密保存到数据库中的。

    1、 在添加用户信息时,自动将用户手机号加密保存到数据中;

    2、 在查询用户信息时,自动解密用户手机号;

    四、实现思路

    1、 编写一个实体类,凡是此实体类的数据都表示需要加解密的;

    public class Encrypt {
        private String value;
    
        public Encrypt() {
        }
    
        public Encrypt(String value) {
            this.value = value;
        }
    
        public String getValue() {
            return value;
        }
    
        public void setValue(String value) {
            this.value = value;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2、编写一个加解密的TypeHandler

    • 设置参数时,加密数据。
    • 从数据库获取记录时,解密数据。
    /**
     * 注意⚠️:
     *
     * @MappedTypes:表示该处理器处理的java类型是什么。
     *
     * @MappedJdbcTypes:表示处理器处理的Jdbc类型。
     */
    @MappedJdbcTypes(JdbcType.VARCHAR)
    @MappedTypes(Encrypt.class)
    public class EncryptTypeHandler extends BaseTypeHandler<Encrypt> {
    
        private static final byte[] KEYS = "12345678abcdefgh".getBytes(StandardCharsets.UTF_8);
    
        /**
         * 设置参数
         */
        @Override
        public void setNonNullParameter(PreparedStatement ps, int i, Encrypt parameter, JdbcType jdbcType) throws SQLException {
            if (parameter == null || parameter.getValue() == null) {
                ps.setString(i, null);
                return;
            }
            //这里引入hutool依赖,对KEYS进行AES加密得到密钥
            AES aes = SecureUtil.aes(KEYS);
            //对Encrypt 类型的参数进行加密
            String encrypt = aes.encryptHex(parameter.getValue());
            ps.setString(i, encrypt);
        }
    
        /**
         * 获取值
         */
        @Override
        public Encrypt getNullableResult(ResultSet rs, String columnName) throws SQLException {
            //在获取值是对加密的数据进行解密
            return decrypt(rs.getString(columnName));
        }
    
        /**
         * 获取值
         */
        @Override
        public Encrypt getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
            return decrypt(rs.getString(columnIndex));
        }
    
        /**
         * 获取值
         */
        @Override
        public Encrypt getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
            return decrypt(cs.getString(columnIndex));
        }
    
        public Encrypt decrypt(String value) {
            if (null == value) {
                return null;
            }
            return new Encrypt(SecureUtil.aes(KEYS).decryptStr(value));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    3、配置文件中指定Typehandler的包路径

    mybatis.type-handlers-package=com.llp.llpmybatis.typehandler
    
    • 1
    #\u914D\u7F6Emybatis
    mybatis:
      configuration:
        #\u5F00\u542F\u4E0B\u5212\u7EBF\u6620\u5C04\u9A7C\u5CF0\u547D\u540D
        map-underscore-to-camel-case: true
        #mybatis\u65E5\u5FD7\u6253\u5370
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        # xml\u6587\u4EF6\u4F4D\u7F6E\u6620\u5C04
      mapper-locations: classpath*:mapper/*.xml
      type-handlers-package: com.llp.llpmybatis.typehandler
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4、sql语句中写法

    测试方法

    @Autowired
    private TblUserDao userDao;
    @Test
    void contextLoads() {
        Encrypt phone = new Encrypt("15023511828");
        String name = "小红";
        Encrypt email = new Encrypt("xiaohong@123.com");
        userDao.insertUser(name,phone,email);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    userDao:需要加密的字段需要传入我们前面指定的类型

    void insertUser(@Param("name") String name,@Param("phone") Encrypt phone,@Param("email") Encrypt email);
    
    • 1

    sql写法:照常写就行,没有什么特殊的地方

    <insert id="insertUser">
       insert  into tbl_user (name,phone,email) values (#{name},#{phone},#{email})
    </insert>
    
    • 1
    • 2
    • 3

    image-20220810174820300

    五、测试结果

    controller

    @RestController
    public class MyTestController {
    
        @Autowired
        private TblUserDao tblUserDao;
    
        @RequestMapping("/test")
        public UserVo mytest(){
            UserVo userVo = tblUserDao.findById(7L);
            return userVo;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    dao

    UserVo findById(@Param("id") Long id);
    
    • 1
    <resultMap id="userVo" type="com.llp.llpmybatis.vo.UserVo">
      <result column="id" jdbcType="BIGINT" property="id" />
        <result column="name" jdbcType="VARCHAR" property="name" />
        <result column="nickname" jdbcType="VARCHAR" property="nickname" />
        <result column="age" jdbcType="INTEGER" property="age" />
        <result column="phone"  property="phone" />
        <result column="email"  property="email" />
        <result column="password" jdbcType="VARCHAR" property="password" />
      resultMap>
    <select id="findById" resultMap="userVo">
    	select * from tbl_user where id = #{id}
    select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    vo

    这里为了不影响实体类字段的类型,用vo来进行映射

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class UserVo {
        private Long id;
    
        private String name;
    
        private String nickname;
    
        private Integer age;
    
        //注意:针对需要解密的字段需要指定我们之前进行加密的类型,这样才能匹配到我们自定义的TypeHandler
        private Encrypt phone;
    
        private Encrypt email;
    
        private String password;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    image-20220810175308017

  • 相关阅读:
    cas:216300-12-8|1-丙基-3-甲基咪唑六氟磷酸盐[C3MIm]PF6离子液体分子量:125
    7、yaml给实体类赋值(用@ConfigurationProperties(参数为yaml对象)扩展名以yml为主流)
    Android之App跳转其他软件
    Redis 异常三连环
    Qt - Qt5事件处理(鼠标事件)
    docker使用Open Policy Agent(OPA)进行访问控制
    Excel二维码图片生成器
    Spring Boot中实现订单30分钟自动取消的策略
    5.外部中断
    Linux设备模型(五) - uevent kernel实现
  • 原文地址:https://blog.csdn.net/qq_44981526/article/details/126271535