Java 使用mybatis的BaseTypeHandler实现数据自动AES加密解密,通过Hutool工具类自定义注解实现数据脱【附有完整步骤和代码】

一、AES加密

1 加密工具类

使用KeyGenerator生成AES算法生成器


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";/*** 生成密钥的种子不可泄露  16位*/public static final String KEY = "xxxxxxxxxxxxxxxx";/*** 生成密钥对象*/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) throws Exception {// 生成密钥对象SecretKey secKey;try {secKey = generateKey(key);// 获取 AES 密码器Cipher cipher = Cipher.getInstance(ALGORITHM);// 初始化密码器(加密模型)cipher.init(Cipher.ENCRYPT_MODE, secKey);// 加密数据, 返回密文byte[] result = new byte[]{};if(!StringUtils.isEmpty(content)){byte[]  plainBytes = content.getBytes("utf-8");result = cipher.doFinal(plainBytes);}return Base64.getEncoder().encodeToString(result);//通过Base64转码返回} catch (Exception e) {throw new Exception( "AES 加密失败:" + e.getMessage());}}/*** 数据解密: 密文 -> 明文*/public static String decrypt(String content, byte[] key) throws Exception {try {// 生成密钥对象SecretKey secKey = generateKey(key);// 获取 AES 密码器Cipher cipher = Cipher.getInstance(ALGORITHM);// 初始化密码器(解密模型)cipher.init(Cipher.DECRYPT_MODE, secKey);// 解密数据, 返回明文byte[] result = new byte[]{};if(!StringUtils.isEmpty(content)){result = cipher.doFinal(Base64.getDecoder().decode(content));}return new String(result, "utf-8");} catch (Exception e) {throw new Exception("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}}}
}

2、TypeHandler类型处理器

使用Mybatis中的TypeHandler类型处理器,定义一个实现自动加密解密的处理器

BaseTypeHandler背景
BaseTypeHandler是Mybatis中的一个基类,他的作用有如下几点:
类型处理器的基类Mybatis中的TypeHandler类型处理器,用于JavaType和jdbcType转换,用于 PreparedStatement 设置参数值和从 ResultSet 或 CallableStatement 中取出一个值。MyBatis 内置了很多TypeHandler可以实现BaseTypeHandler,自定义 TypeHandler


@MappedJdbcTypes(JdbcType.VARCHAR)
public class AESEncryptHandler extends BaseTypeHandler<Object> {@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {String content = (parameter == null) ? "" : parameter.toString();try {ps.setString(i, AESUtil.encrypt(content, AESUtil.KEY.getBytes()));} catch (Exception e) {throw new RuntimeException(e);}}@Overridepublic Object getNullableResult(ResultSet rs, String columnName) throws SQLException {String columnValue = rs.getString(columnName);try {return AESUtil.decrypt(columnValue,  AESUtil.KEY.getBytes());} catch (Exception e) {throw new RuntimeException(e);}}@Overridepublic Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException {String columnValue = rs.getString(columnIndex);try {return AESUtil.decrypt(columnValue,  AESUtil.KEY.getBytes());} catch (Exception e) {throw new RuntimeException(e);}}@Overridepublic Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {String columnValue = cs.getString(columnIndex);try {return AESUtil.decrypt(columnValue,  AESUtil.KEY.getBytes());} catch (Exception e) {throw new RuntimeException(e);}}
}

3、在实体Bean的字段上使用注解

    /*** 手机号码 AES加密*/@ApiModelProperty(value = "手机号码")@TableField(typeHandler = AESEncryptHandler.class)private java.lang.String phone;

4、自定义SQL中使用resultMap

此时在mybatis plus中是可以正常使用的,但是当我们在xml中自定义SQL文件时无效,这时需要在xml中定义resultMap

(1) 在实体中设置:autoResultMap = true

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "sys_user",autoResultMap = true)
public class SysUser extends BaseBean implements Serializable {
}

(2)在xml中设置返回数据类型

<resultMap id="objectMap" type="com.depu.ems.bean.system.SysUser"><id column="id" property="id" /><result property="phone" column="phone" typeHandler="com.depu.ems.common.util.AESEncryptHandler"/></resultMap><select id="getObjectList"  resultMap="objectMap">SELECT *     FROM table_name</select>

需要手机加密解密的使用场景:
1)xml中自定义sql 返回实体类不能解密,返回resultMap在map中配置typeHandler可以解密。
2)自带的wrappers更新不能加密,需要将数据加密后更新。

二、数据脱敏

1、引入hutool

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

2、配合JackSon通过注解方式实现脱敏

(1)定义脱敏的枚举类


public enum DesensitizationTypeEnum {//自定义MY_RULE,//用户idUSER_ID,//中文名CHINESE_NAME,//身份证号ID_CARD,//座机号FIXED_PHONE,//手机号MOBILE_PHONE,//地址ADDRESS,//电子邮件EMAIL,//密码PASSWORD,//中国大陆车牌,包含普通车辆、新能源车辆CAR_LICENSE,//银行卡BANK_CARD
}

(2)定义一个用于脱敏的 Desensitization 注解


/*** @Retention(RetentionPolicy.RUNTIME):运行时生效。* @Target(ElementType.FIELD):可用在字段上。* @JacksonAnnotationsInside:此注解可以点进去看一下是一个元注解,主要是用户打包其他注解一起使用。* @JsonSerialize:上面说到过,该注解的作用就是可自定义序列化,可以用在注解上,方法上,字段上,类上,运行时生效等等,根据提供的序列化类里面的重写方法实现自定义序列化。*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizationSerialize.class)
public @interface Desensitization {/*** 脱敏数据类型,在MY_RULE的时候,startInclude和endExclude生效*/DesensitizationTypeEnum type() default DesensitizationTypeEnum.MY_RULE;/*** 脱敏开始位置(包含)*/int startInclude() default 0;/*** 脱敏结束位置(不包含)*/int endExclude() default 0;
}

(3)创建自定的序列化类


import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.DesensitizedUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import org.jeecg.common.constant.enums.DesensitizationTypeEnum;
import org.jeecg.common.system.annotation.Desensitization;import java.io.IOException;
import java.util.Objects;/*** @description: 自定义序列化类* @author: Zhangxue* @time: 2023/12/15 15:57*/
@AllArgsConstructor
@NoArgsConstructor
public class DesensitizationSerialize extends JsonSerializer<String> implements ContextualSerializer {private DesensitizationTypeEnum type;private Integer startInclude;private Integer endExclude;@Overridepublic void serialize(String str, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {switch (type) {// 自定义类型脱敏case MY_RULE:jsonGenerator.writeString(CharSequenceUtil.hide(str, startInclude, endExclude));break;// userId脱敏case USER_ID:jsonGenerator.writeString(String.valueOf(DesensitizedUtil.userId()));break;// 中文姓名脱敏case CHINESE_NAME:jsonGenerator.writeString(DesensitizedUtil.chineseName(String.valueOf(str)));break;// 身份证脱敏case ID_CARD:jsonGenerator.writeString(DesensitizedUtil.idCardNum(String.valueOf(str), 1, 2));break;// 固定电话脱敏case FIXED_PHONE:jsonGenerator.writeString(DesensitizedUtil.fixedPhone(String.valueOf(str)));break;// 手机号脱敏case MOBILE_PHONE:jsonGenerator.writeString(DesensitizedUtil.mobilePhone(String.valueOf(str)));break;// 地址脱敏case ADDRESS:jsonGenerator.writeString(DesensitizedUtil.address(String.valueOf(str), 8));break;// 邮箱脱敏case EMAIL:jsonGenerator.writeString(DesensitizedUtil.email(String.valueOf(str)));break;// 密码脱敏case PASSWORD:jsonGenerator.writeString(DesensitizedUtil.password(String.valueOf(str)));break;// 中国车牌脱敏case CAR_LICENSE:jsonGenerator.writeString(DesensitizedUtil.carLicense(String.valueOf(str)));break;// 银行卡脱敏case BANK_CARD:jsonGenerator.writeString(DesensitizedUtil.bankCard(String.valueOf(str)));break;default:}}@Overridepublic JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {if (beanProperty != null) {// 判断数据类型是否为String类型if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {// 获取定义的注解Desensitization desensitization = beanProperty.getAnnotation(Desensitization.class);// 为nullif (desensitization == null) {desensitization = beanProperty.getContextAnnotation(Desensitization.class);}// 不为nullif (desensitization != null) {// 创建定义的序列化类的实例并且返回,入参为注解定义的type,开始位置,结束位置。return new DesensitizationSerialize(desensitization.type(), desensitization.startInclude(),desensitization.endExclude());}}return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);}return serializerProvider.findNullValueSerializer(null);}
}

3、使用

使用@Desensitization注解实现脱敏

    /*** 手机号码 AES加密、脱敏*/@ApiModelProperty(value = "手机号码")@TableField(typeHandler = AESEncryptHandler.class)@Desensitization(type = DesensitizationTypeEnum.MOBILE_PHONE)private java.lang.String phone;

三、测试运行结果

1、增
测试程序:
在这里插入图片描述
调用:
在这里插入图片描述
数据库中结果:
在这里插入图片描述

2、查
测试程序:
在这里插入图片描述
调用:
在这里插入图片描述

3、改
测试程序:
在这里插入图片描述
调用:
在这里插入图片描述
数据库结果:
在这里插入图片描述

再次查询:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/225611.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【CDP】CDP 集群通过Knox 访问Yarn Web UI,无法跳转到Flink Web UI 问题解决

一、前言 记录下在CDP 环境中&#xff0c;通过Knox 访问Yarn Web UI&#xff0c;无法跳转到Flink Web UI 的BUG 解决方法。 二、问题复现 登录 Knox Web UI 找到任一 Flink 任务 点击 ApplicationMaster 跳转 Flink WEB UI 出问题 内容空白&#xff0c;无法正常跳转到…

JS基本语法

JS基本语法 变量数据类型原始数据类型 函数定义第一种方式第二种方式 JS 对象ArrayStringJavaScript 自定义对象JSONDOMBOM JS 事件事件监听事件绑定常见事件 变量 数据类型 原始数据类型 函数定义 第一种方式 第二种方式 JS 对象 Array String JavaScript 自定义对象 JSON …

向华为学习:基于BLM模型的战略规划研讨会实操的详细说明,含研讨表单(一)

前面&#xff0c;华研荟用了三篇文章介绍华为战略规划的时候使用的其中一个工具&#xff1a;五看三定。一句话来说&#xff0c;五看三定是通过“五看”来知己知彼&#xff0c;然后设计业务&#xff0c;在选定的业务领域&#xff08;方向&#xff09;确定战略控制点&#xff0c;…

STM32_HAL库—IWDG看门狗

一、CubeMX设置 1、晶振配置&#xff08;72M&#xff09; 2、数据配置 超时时间 Tout prv / LSI * rlv (s) 其中prv是预分频器寄存器的值&#xff0c;rlv是重装载寄存器的值&#xff0c;而LSI值默认是40kHz&#xff0c;如下所示。 3、代码实现 int main(){while(1){HAL_IW…

【c++】stl_priority_queue优先级队列

目录 一、priority_queue的介绍 二、 priority_queue的本质 三、priority_queue的使用 四、priority_queue的模拟实现 总结 一、priority_queue的介绍 首先让我们通过阅读优先级队列的官方文档 简单翻译一下 1. 优先队列是一种容器适配器&#xff0c;根据严格的弱排序标准…

MySQL数据库遇到不规范建表问题解决方案

简介&#xff1a; 需要建立的关联表如上图所示。 问题发现&#xff1a; 好&#xff0c;问题来了&#xff0c;大伙儿请看&#xff1a;我们的organizations表中的Industry字段居然存储了两个IndustryName&#xff0c;这就很恶心了&#xff0c;就需要我们进行拆分和去重后放到In…

【vtkWidgetRepresentation】第十二期 vtkBalloonRepresentation

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 前言 本文分享vtkBalloonRepresentation,用于标注文字或图片,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 1. vtkBalloonRepre…

竞赛保研 opencv 图像识别 指纹识别 - python

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于机器视觉的指纹识别系统 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 该项目较为新颖&#xff0c;适…

标书设计:目录的必要性与优化建议

标书&#xff0c;作为商务文件的一种&#xff0c;旨在展示公司实力、产品优势和服务水平&#xff0c;是企业开展商业活动的一项重要工具。在进行标书制作时&#xff0c;有人认为是否需要目录&#xff0c;成为了一个值得讨论的问题。 目录作为标书的导航&#xff0c;是否必要呢&…

Excel实现字母+数字拖拉自动递增,步长可更改

目录 1、带有字母的数字序列自增加&#xff08;步长可变&#xff09; 2、仅字母自增加 3、字母数字同时自增 1、带有字母的数字序列自增加&#xff08;步长可变&#xff09; 使用Excel通常可以直接通过拖拉的方式&#xff0c;实现自增数字&#xf…

Java报错-Non-terminating decimal expansion; no exact representable decimal result

1. 背景 在使用 BigDecimal 的 divide() 对两个数相除时&#xff0c;报了如题的错误。 public class Test {public static void main(String[] args) {BigDecimal b1 new BigDecimal(1);BigDecimal b2 new BigDecimal(3);System.out.println(b1.divide(b2)); // Sys…

单口千兆以太网物理层芯片

一、基本介绍 YT8521S是一款单口千兆以太网物理层芯片&#xff0c;YT8521S是一款高度集成的以太网收发器&#xff0c;符合10BASE-Te、100BASE-TX和1000BASE-T IEEE 802.3标准。它提供了传输和接收所需的所有物理层功能通过CAT.5E UTP电缆的以太网数据包。 YT8521S采用最先进的…

【Unity动画】综合案例完结-控制角色动作播放+声音配套

这个案例实现的动作并不复杂&#xff0c;主要包含一个 跳跃动作、攻击动作、还有一个包含三个动画状态的动画混合树。然后设置三个参数来控制切换。 状态机结构如下&#xff1a; 完整代码 using System.Collections; using System.Collections.Generic; using UnityEngine;pu…

字符设备驱动模块的编译

一. 简介 本文继上一篇文章的学习&#xff0c;上一篇文章学习了字符设备驱动框架的初步编写。文章地址如下&#xff1a; 字符设备驱动框架的编写-CSDN博客 本文对上一篇编写的驱动模块初步框架进行编译。 二. 字符设备驱动模块的编译 上一篇文章&#xff0c;编写了字符设备…

10、神秘的“位移主题”

神秘的“位移主题” 1、什么是位移主题2、位移主题的消息格式3、位移主题是怎么被创建的4、什么地方会用到位移主题5、位移主题的删除机制 本章主题是&#xff1a;Kafka 中的内部主题&#xff08;Internal Topic&#xff09;__consumer_offsets。 __consumer_offsets 在 Kafka …

PHPRunner 10.91 Crack

PHPRunner是一款非常好用的网页制作工具&#xff0c;界面简洁美观&#xff0c;支持处理多个数据库连接并添加设计页面&#xff0c;页面中可以显示不同的不相关对象&#xff0c;如网格&#xff0c;单个记录&#xff0c;图表&#xff0c;报告等。PHPRunner支持多个操作系统&#…

【一起学Rust | 框架篇 | Tauri2.0框架】Tauri App开启远程调试功能

文章目录 前言一、搭建PageSpy环境二、接入SDK三、进行远程调试调试控制台网络抓包审查元素 四、延伸 前言 Tauri在Rust圈内成名已久&#xff0c;凭借Rust的可靠性&#xff0c;使用系统原生的Webview构建更小的App 以及开发人员可以灵活的使用各种前端框架而一战成名。 然而&…

批量识别名片并转换为Excel:提高工作效率的实用技巧

随着数字化的快速发展&#xff0c;很多传统的工作也开始向电子化转型。而名片管理就是其中之一。许多人会遇到与题目相似的问题&#xff1a;拥有大量名片&#xff0c;但却不方便携带和管理。 批量识别名片并将其转换为Excel格式是一个很好的想法&#xff0c;这不仅可以提高你的…

用Bat文件调用小牛翻译api快速翻译

为了帮助大家更加轻松地调用机器翻译api&#xff0c;本人探索实现了一种可以通过BAT文件来调用机器翻译api&#xff0c;对粘贴板中的文本进行翻译&#xff0c;并将翻译结果保存为txt文件。下面把实现步骤简要说明如下&#xff1a; 第一步&#xff1a;获取小牛机器翻译api 进入…