开发者

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

目录
  • 一、介绍
  • 二、解决思路
  • 三、实现
    • 1. 加解密方法: 这里的加解密方法就直接使用hutool的des加密了。
    • 2. 编写自定义的TypeHandler,继承自BaseTypeHandler
    • 3. 注册自定义的typeHandler到myBATis
    • 4. 使用

一、介绍

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

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

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

二、解决思路

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

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

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

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

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

三、实现

1. 加解密方法: 这里的加解密方法就直接使用hutool的des加密了。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
  
  		<!-- hutool-all 含接加密工具 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.15</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>

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> {
 
    privhttp://www.devze.comate 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 columnIndfhmSipHiex) 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;
    }

}

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

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;
}

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);

}

xml文件 UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!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"/>
        <!-- 结果集里需要解密的字段,加上typeHandler -->
        <result column="phone" property="phone" typeHandler="com.zsx.common.CryptoTypeHandler" />
    </resultMap>

 	<!-- sql传参里需要加密的字段,js加上typeHandler -->
    <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>

	<!-- sql传参里需要加密的字段,加上typeHandler -->
    <select id="findByPhone" resultMap="BaseResultMap">
        s编程客栈elect id, name, phone
        from user
        where phone = #{phone,typeHandler=com.zsx.common.CryptoTypeHandler}
    </select>

	<!-- sql传参里需要加密的字段,加上typeHandler -->
    <select id="findByPhone2" resultMap="BaseResultMap">
        select id, name, phone
        from user
        where phone = #{phone,typeHandler=com.zsx.common.CryptoTypeHandler}
    </select>

</mapper>

最后的测试代码 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.printandroidln(userList.toString());
    }

}

项目结构如下

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

需要注意:

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

到此这篇关于Mybatis基于TypeHandler实现敏感数据加密的文章就介绍到这了,更多相关Mybatis TypeHandler敏感数据加密内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)! 

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新开发

开发排行榜