• 【Mybatis】基于TypeHandler实现敏感数据加密


    一、介绍

    业务场景中经常会遇到诸如用户手机号,身份证号,银行卡号,邮箱,地址,密码等等信息,属于敏感信息,需要保存在数据库中。而很多公司会会要求对数据库中的此类数据进行加密存储。

    敏感数据脱敏需要处理的两个问题:

    1. 查询操作,需要对查询的关键字进行加密,同时也要对从库中查到的数据进行解密
    2. 插入和更新操作,需要对插入或者更新的数据进行加密,然后保存到数据库

    二、解决思路

    使用mybatis框架提供的TypeHandler来实现在持久层处理数据。

    Typehandlermybatis提供的一个接口,通过实现这个接口,可以实现jdbc类型数据和java类型数据的转换,我们常看到的varchar转string、bigint转long等都是mybatis自身实现此接口处理的。

    在这里插入图片描述

    因此,可以自己实现一个Typehandler,满足自己的数据处理需求。

    优点:实现也简单,使用方便,整个使用过程只需要对xml代码做修改

    三、实现

    1. 加解密方法: 这里的加解密方法就直接使用hutool的des加密了。
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
      
      		
            <dependency>
                <groupId>cn.hutoolgroupId>
                <artifactId>hutool-allartifactId>
                <version>5.8.15version>
            dependency>
    
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
            
            
            <dependency>
                <groupId>org.mybatis.spring.bootgroupId>
                <artifactId>mybatis-spring-boot-starterartifactId>
                <version>2.2.2version>
            dependency>
    
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <version>8.0.33version>
            dependency>
    
    • 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
    2. 编写自定义的TypeHandler,继承自BaseTypeHandler
    package com.zsx.common;
    
    import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
    import cn.hutool.crypto.symmetric.SymmetricCrypto;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.ibatis.type.BaseTypeHandler;
    import org.apache.ibatis.type.JdbcType;
    import org.apache.ibatis.type.MappedJdbcTypes;
    import org.apache.ibatis.type.MappedTypes;
     
    import java.sql.CallableStatement;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    /**
     * @Description typeHandler加解密处理器 将String类型的字段加密或解密
     * @author zhousx
     * @Data 2023-10-15 13:02
     */
    @Slf4j
    @MappedJdbcTypes(JdbcType.VARCHAR)
    @MappedTypes(String.class)
    public class CryptoTypeHandler extends BaseTypeHandler<String> {
     
        private final byte[] key = {-26, -70, -29, -99, 73, -82, 91, -50, 79, -77, 59, 104, 2, -36, 50, -22, -39, -15, -57, -89, 81, -99, 42, -89};
     
        private final SymmetricCrypto des = new SymmetricCrypto(SymmetricAlgorithm.DESede, key);
     
     
        /*
         * 加工入参
         */
        @Override
        public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
            if (parameter != null) {
                //加密
                String encryptHex = des.encryptHex(parameter);
                log.info("{} ---加密为---> {}", parameter, encryptHex);
                ps.setString(i, encryptHex);
            }
        }
     
        /*
         * 根据列名获取返回结果,可在此方法中加工返回值
         */
        @Override
        public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
            String originRes = rs.getString(columnName);
            if (originRes != null) {
                String res = des.decryptStr(originRes);
                log.info("{} ---解密为---> {}", originRes, res);
                return res;
            }
            log.info("结果为空,无需解密");
            return null;
        }
     
        /*
         * 根据列下标获取返回结果,可在此方法中加工返回值
         */
        @Override
        public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
            String originRes = rs.getString(columnIndex);
            if (originRes != null) {
                String res = des.decryptStr(originRes);
                log.info("{} ---解密为---> {}", originRes, res);
                return res;
            }
            log.info("结果为空,无需解密");
            return null;
        }
     
        /*
         * 根据列下标获取返回结果(存储过程),可在此方法中加工返回值
         */
        @Override
        public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
            String originRes = cs.getString(columnIndex);
            if (originRes != null) {
                String res = des.decryptStr(originRes);
                log.info("{} ---解密为---> {}", originRes, res);
                return res;
            }
            log.info("结果为空,无需解密");
            return null;
        }
     
    }
    
    • 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
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    3. 注册自定义的typeHandler到mybatis

    application.yml

    server:
      port: 8082
    
    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&characterEncoding=utf8&useSSL=true
        username: root
        password: 123456
    
    
    mybatis:
      # 编写好的TypeHandler需要注册到mybatis中
      type-handlers-package: com.zsx.cryptotypehandler.common
      mapper-locations: classpath:mapper/*.xml
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    4. 使用

    实体类 User.java

    package com.zsx.entity;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
    
    
        private int id;
    
        private String name;
    
        private String phone;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    dao层 IUserDao.java

    package com.zsx.dao;
     
    import com.zsx.entity.User;
    import org.apache.ibatis.annotations.Mapper;
     
    import java.util.List;
    
    /**
     * userMapper
     */
    @Mapper
    public interface IUserDao {
     
        int insertEncrypt(User user);
     
        List<User> findByName(User user);
    
        List<User> findByPhone(User user);
    
        List<User> findByPhone2(String phone);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    xml文件 UserMapper.xml

    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.zsx.dao.IUserDao">
    
        
        <resultMap id="BaseResultMap" type="com.zsx.entity.User">
            <id column="id" property="id" />
            <result column="name" property="name"/>
            
            <result column="phone" property="phone" typeHandler="com.zsx.common.CryptoTypeHandler" />
        resultMap>
    
     	
        <insert id="insertEncrypt">
            insert into user (id, name, phone)
            values (#{id}, #{name}, #{phone,typeHandler=com.zsx.common.CryptoTypeHandler})
        insert>
    
        <select id="findByName" resultMap="BaseResultMap">
            select id, name, phone
            from user
            where name = #{name}
        select>
    
    	
        <select id="findByPhone" resultMap="BaseResultMap">
            select id, name, phone
            from user
            where phone = #{phone,typeHandler=com.zsx.common.CryptoTypeHandler}
        select>
    
    	
        <select id="findByPhone2" resultMap="BaseResultMap">
            select id, name, phone
            from user
            where phone = #{phone,typeHandler=com.zsx.common.CryptoTypeHandler}
        select>
    
    mapper>
    
    • 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

    最后的测试代码 EncryptTypeHandlerTest.java

    package com.zsx;
    
    import com.zsx.dao.IUserDao;
    import com.zsx.entity.User;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import java.util.List;
    
    @SpringBootTest
    public class EncryptTypeHandlerTest {
    
        @Autowired
        private IUserDao userDao;
    
        @Test
        public void testInsert(){
            User user = new User();
            user.setPhone("11112222333");
            user.setName("zsx");
            int result = userDao.insertEncrypt(user);
            System.out.println(result);
        }
    
        @Test
        public void testSelectByName(){
            User user = new User();
            user.setName("zsx");
            List<User> userList = userDao.findByName(user);
            System.out.println(userList.toString());
        }
    
        @Test
        public void testSelectByPhone(){
            User user = new User();
            user.setPhone("11112222333");
            List<User> userList = userDao.findByPhone(user);
            System.out.println(userList.toString());
        }
    
        @Test
        public void testSelectByPhone2(){
            List<User> userList = userDao.findByPhone2("11112222333");
            System.out.println(userList.toString());
        }
    
    }
    
    
    • 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
    项目结构如下

    在这里插入图片描述

    需要注意:
    sql查询的结果,必须要使用 resultMap 映射,否则就不能完成结果字段的自动解密

  • 相关阅读:
    算法 二叉树的遍历(迭代法/递归法)
    Android Toast居中显示方法二
    【iOS开发】——事件传递链和事件响应链
    Qt之使用QTreeView实现QQ登录好友列表
    猿创征文 | 2023年必须掌握的DevOps工具推荐(一)
    国内数据防泄漏产品选型指南
    圈重点|等保2.0灾备方案怎么做?
    Pytest----如何通过pytest.ini配置不显示告警或将告警报错
    【数据结构】二叉树基础入门
    C语言进阶篇——深度解剖数据在内存中的存储(配练习)
  • 原文地址:https://blog.csdn.net/zsx1713366249/article/details/133840851