mybatisplus配置拦截器实现保存加密,输出解密,模糊查询

前言:因公司需求需要把某些实体类的某些字段值进行加密保存,在查询时解密明文输出。现记录两种方式。

一、第一种方式:

(1)使用@TableField(typeHandler = TypeHandler.class)注解自带的字段类型处理器,写一个 handle 类继承 BaseTypeHandler。本身这个是用来处理字段类型转换的,如map转list之类的,这里也可以用作值加密。

注:使用这种方式有个前提,需要在@TableName注解上加上autoResultMap = true,才能使查询生效,否则只是新增修改生效进行拦截。没看源码猜想应该是先查整个实体有没有autoResultMap = true,再逐个进行字段拦截处理优化效率。

@Data
@TableName(value = "xxx",autoResultMap = true)
public class xxx implements Serializable {private static final long serialVersionUID = 1L;@TableId(type = IdType.NONE, value = "id")private String id;@ApiModelProperty(value = "用户名")@TableField(value = "username",typeHandler = TypeControlHandler.class)private String username;@ApiModelProperty(value = "手机号")@TableField(value = "phone",typeHandler = TypeControlHandler.class)private String phone;
}

(2)定义加解密方式-自定义(这里配合使用了mysql的AES_ENCRYPTAES_DECRYPT函数加解密,主要是为了实现模糊查询,具体加解密方式可以自定义)

//需要导包:
//<dependency>
//	<groupId>commons-codec</groupId>
//	<artifactId>commons-codec</artifactId>
//</dependency>import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;@Slf4j
public class DBAesUtils {
/** AES 加解密密钥,请勿擅自修改!!! */public static final String key = "唯一加解密密匙key-自定义";/*** AES 加密 使用AES-128-ECB加密模式* @param sSrc  需要加密的字段* @param sKey  16 位密钥* @return* @throws Exception*/public static String Encrypt(String sSrc, String sKey) {try {if (sKey == null) {return null;}/** 判断Key是否为16位 */if (sKey.length() != 16) {return null;}byte[] raw = sKey.getBytes("utf-8");SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");/** "算法/模式/补码方式" */Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");cipher.init(Cipher.ENCRYPT_MODE, skeySpec);byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));/** 此处使用BASE64做转码功能,同时能起到2次加密的作用。 */return new Base64().encodeToString(encrypted);} catch (Exception e) {e.printStackTrace();return null;}}public static String Encrypt(String sSrc) {return Encrypt(sSrc, key);}/*** AES 解密 使用AES-128-ECB加密模式* @param sSrc  需要解密的字段* @param sKey  16 位密钥* @return* @throws Exception*/public static String Decrypt(String sSrc, String sKey) {try {// 判断Key是否正确if (sKey == null) {return null;}// 判断Key是否为16位if (sKey.length() != 16) {return null;}byte[] raw = sKey.getBytes("utf-8");SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");cipher.init(Cipher.DECRYPT_MODE, skeySpec);/** 先用base64解密 */byte[] encrypted1 = new Base64().decode(sSrc);try {byte[] original = cipher.doFinal(encrypted1);String originalString = new String(original,"utf-8");return originalString;} catch (Exception e) {System.out.println(e.toString());return null;}} catch (Exception e) {e.printStackTrace();return null;}}public static String Decrypt(String sSrc) {return Decrypt(sSrc, key);}}

(3)定义具体拦截处理实现

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;public class TypeControlHandler extends BaseTypeHandler<String> {@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {ps.setString(i, DBAesUtils.Encrypt(parameter));}@Overridepublic String getNullableResult(ResultSet rs, String columnName) throws SQLException {return DBAesUtils.Decrypt(rs.getString(columnName));}@Overridepublic String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {return DBAesUtils.Decrypt(rs.getString(columnIndex));}@Overridepublic String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {return DBAesUtils.Decrypt(cs.getString(columnIndex));}}

(4)最终实现结果(有个小问题就是如果加密前字符一样,加密出来密文就是一样的)
在这里插入图片描述
至于加密的字段查询并没有想到好的方法,目前想到的只能不使用LambdaQueryWrapper,使用QueryWrapper写死条件进行查询,这里配合了mysql的加解密函数,其他数据库需更改加解密方式才能使用。

wrapper.like("AES_DECRYPT(FROM_BASE64(字段名),'" + key + "')", 查询值);

第二种方式(其实实现原理就是第一种方式,只不过加了相对来说更灵活的扩展):

(1)定义两个注解

/*** 需要加解密的字段用这个注解* @author wangshaopeng@talkweb.com.cn* @Date 2023-05-31*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface EncryptedColumn {}/*** 需要加解密的实体类用这个注解* @author wangshaopeng@talkweb.com.cn* @Date 2023-05-31*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EncryptedTable {}

(2)实现InnerInterceptor进行拦截加密操作(使用第一种方法的DBAesUtils工具类进行加密)

import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
import com.baomidou.mybatisplus.core.conditions.update.Update;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.springframework.core.annotation.AnnotationUtils;import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;@SuppressWarnings({"rawtypes"})
public class EncryptInterceptor extends JsqlParserSupport implements InnerInterceptor {/*** 变量占位符正则*/private static final Pattern PARAM_PAIRS_RE = Pattern.compile("#\\{ew\\.paramNameValuePairs\\.(" + Constants.WRAPPER_PARAM + "\\d+)\\}");@Overridepublic void beforeUpdate(Executor executor, MappedStatement mappedStatement, Object parameterObject) throws SQLException {if (Objects.isNull(parameterObject)) {return;}// 通过MybatisPlus自带API(save、insert等)新增数据库时if (!(parameterObject instanceof Map)) {if (needToDecrypt(parameterObject.getClass())) {encryptEntity(parameterObject);}return;}Map paramMap = (Map) parameterObject;Object param;// 通过MybatisPlus自带API(update、updateById等)修改数据库时if (paramMap.containsKey(Constants.ENTITY) && null != (param = paramMap.get(Constants.ENTITY))) {if (needToDecrypt(param.getClass())) {encryptEntity(param);}return;}// 通过在mapper.xml中自定义API修改数据库时if (paramMap.containsKey("entity") && null != (param = paramMap.get("entity"))) {if (needToDecrypt(param.getClass())) {encryptEntity(param);}return;}// 通过UpdateWrapper、LambdaUpdateWrapper修改数据库时if (paramMap.containsKey(Constants.WRAPPER) && null != (param = paramMap.get(Constants.WRAPPER))) {if (param instanceof Update && param instanceof AbstractWrapper) {Class<?> entityClass = mappedStatement.getParameterMap().getType();if (needToDecrypt(entityClass)) {encryptWrapper(entityClass, param);}}return;}}/*** 校验该实例的类是否被@EncryptedTable所注解*/private boolean needToDecrypt(Class<?> objectClass) {try {EncryptedTable sensitiveData = AnnotationUtils.findAnnotation(objectClass, EncryptedTable.class);return Objects.nonNull(sensitiveData);}catch (Exception ex){return  false;}}/*** 通过API(save、updateById等)修改数据库时** @param parameter*/private void encryptEntity(Object parameter) {//取出parameterType的类Class<?> resultClass = parameter.getClass();Field[] declaredFields =  ReflectUtil.getFields(resultClass);for (Field field : declaredFields) {//取出所有被EncryptedColumn注解的字段EncryptedColumn sensitiveField = field.getAnnotation(EncryptedColumn.class);if (!Objects.isNull(sensitiveField)) {field.setAccessible(true);Object object = null;try {object = field.get(parameter);} catch (IllegalAccessException e) {continue;}//只支持String的解密if (object instanceof String) {String value = (String) object;//对注解的字段进行逐一加密try {field.set(parameter, DBAesUtils.Encrypt(value));} catch (IllegalAccessException e) {continue;}}}}}/*** 通过UpdateWrapper、LambdaUpdateWrapper修改数据库时** @param entityClass* @param ewParam*/private void encryptWrapper(Class<?> entityClass, Object ewParam) {AbstractWrapper updateWrapper = (AbstractWrapper) ewParam;String sqlSet = updateWrapper.getSqlSet();String[] elArr = sqlSet.split(",");Map<String, String> propMap = new HashMap<>(elArr.length);Arrays.stream(elArr).forEach(el -> {String[] elPart = el.split("=");propMap.put(elPart[0], elPart[1]);});//取出parameterType的类Field[] declaredFields = ReflectUtil.getFields(entityClass);for (Field field : declaredFields) {//取出所有被EncryptedColumn注解的字段EncryptedColumn sensitiveField = field.getAnnotation(EncryptedColumn.class);if (Objects.isNull(sensitiveField)) {continue;}String el = propMap.get(field.getName());Matcher matcher = PARAM_PAIRS_RE.matcher(el);if (matcher.matches()) {String valueKey = matcher.group(1);Object value = updateWrapper.getParamNameValuePairs().get(valueKey);updateWrapper.getParamNameValuePairs().put(valueKey, DBAesUtils.Encrypt(value.toString()));}}}}

(3)实现Interceptor进行拦截解密


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import org.springframework.core.annotation.AnnotationUtils;
import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Objects;@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class DecryptInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {Object resultObject = invocation.proceed();if (Objects.isNull(resultObject)) {return null;}if(resultObject instanceof IPage){IPage page = (IPage) resultObject;if(page!=null&& CollUtil.isNotEmpty(page.getRecords())){if (needToDecrypt(page.getRecords().get(0))) {for (Object result : page.getRecords()) {//逐一解密decrypt(result);}}}}else if (resultObject instanceof ArrayList) {//基于selectListArrayList resultList = (ArrayList) resultObject;if (CollUtil.isNotEmpty(resultList) && needToDecrypt(resultList.get(0))) {for (Object result : resultList) {//逐一解密decrypt(result);}}} else if (needToDecrypt(resultObject)) {//基于selectOnedecrypt(resultObject);}return resultObject;}/*** 校验该实例的类是否被@EncryptedTable所注解*/private boolean needToDecrypt(Object object) {Class<?> objectClass = object.getClass();EncryptedTable sensitiveData = AnnotationUtils.findAnnotation(objectClass, EncryptedTable.class);return Objects.nonNull(sensitiveData);}@Overridepublic Object plugin(Object o) {if(o instanceof ResultSetHandler) {return   Plugin.wrap(o, this);}else{return o;}}private <T> T decrypt(T result) throws Exception {//取出resultType的类Class<?> resultClass = result.getClass();Field[] declaredFields = ReflectUtil.getFields(resultClass);for (Field field : declaredFields) {//取出所有被EncryptedColumn注解的字段EncryptedColumn sensitiveField = field.getAnnotation(EncryptedColumn.class);if (!Objects.isNull(sensitiveField)) {field.setAccessible(true);Object object = field.get(result);//只支持String的解密if (object instanceof String) {String value = (String) object;//对注解的字段进行逐一解密String results =  DBAesUtils.Decrypt(value);if(StrUtil.isNotEmpty(results)){field.set(result, results);}else{field.set(result, value);}}}}return result;}
}

注:若是作为单独的starter进行封装,此拦截器需要定义在spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.xxx.xxx.xxx.xxx.DecryptInterceptor

(4)实体类加上注解(需要@EncryptedTable@EncryptedColumn配合使用)

@Data
@TableName(value = "xxx",autoResultMap = true)
@EncryptedTable
public class xxx implements Serializable {private static final long serialVersionUID = 1L;@TableId(type = IdType.NONE, value = "id")private String id;@ApiModelProperty(value = "用户名")@TableField(value = "username")@EncryptedColumnprivate String username;@ApiModelProperty(value = "手机号")@TableField(value = "phone")@EncryptedColumnprivate String phone;
}

实现结果与第一种方式一样

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

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

相关文章

电脑死机的时候,CPU到底在做什么?

电脑死机&#xff0c;应该每个接触计算机的小伙伴都经历过吧。 尤其是早些年&#xff0c;电脑配置还没现在这么高的时候&#xff0c;多开几个重量级应用程序&#xff0c;死机就能如约而至&#xff0c;就算你把键盘上的CTRLALTDELETE按烂了&#xff0c;任务管理器也出不来&…

Mybatis-Genertor逆向工程

1、导入mybaties插件 <build><plugins><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.4.2</version><dependencies><dependency>…

Error: svn: E155004: Run ‘svn cleanup‘ to remove locks

解决办法如下&#xff1a;点击settings 点击清除缓存按钮&#xff0c;然后再使用svn进行提交更新操作&#xff0c;但是可能还会有其它的错误&#xff0c;比如svn: E230001: Server SSL certificate verification failed&#xff0c;解决这个错误请参考我另一篇文章&#xff1a;…

博客系统(升级(Spring))(一)创建数据库,创建实例化对象,统一数据格式,统一报错信息

博客系统&#xff08;一&#xff09; 博客系统一、创建项目二、建立数据库结构链接服务器和数据库和Redis 三、创建实例化对象四、统一数据结构结构 五、统一报错信息 博客系统 博客系统是干什么的&#xff1f; CSDN就是一个典型的博客系统。而我在这里就是通过模拟实现一个博…

Python+Requests+Excel接口测试实战

1、EXCEL文件接口保存方式&#xff0c;如图。 2、然后就是读取EXCEL文件中的数据方法&#xff0c;如下&#xff1a; 1 import xlrd2 3 4 class readExcel(object):5 def __init__(self, path):6 self.path path7 8 property9 def getSheet(self): 10 …

莫比乌斯召回系统介绍

当前召回系统只能召回相关性高的广告&#xff0c;但不能保证该广告变现能力强。莫比乌斯做了如下两点创新&#xff1a; 在召回阶段&#xff0c;引入CPM等业务指标作为召回依据在召回阶段&#xff0c;引入CTR模型&#xff0c;从而召回更多相关性高且变现能力强的广告 参考 百度…

基于Protege的知识建模实战

一.Protege简介、用途和特点 1.Protege简介 Protege是斯坦福大学医学院生物信息研究中心基于Java开发的本体编辑和本体开发工具&#xff0c;也是基于知识的编辑器&#xff0c;属于开放源代码软件。这个软件主要用于语义网中本体的构建&#xff0c;是语义网中本体构建的核心开发…

Elasticsearch:什么是生成式人工智能?

生成式人工智能定义 给学生的解释&#xff08;基本&#xff09;&#xff1a; 生成式人工智能是一种可以创造新的原创内容的技术&#xff0c;例如艺术、音乐、软件代码和写作。 当用户输入提示时&#xff0c;人工智能会根据从互联网上现有示例中学到的知识生成响应&#xff0c;…

linux安装Sentinal1.8.6

前言&#xff1a; 使用docker search sentinel-dashboard命令&#xff0c;发现docker中的镜像版本过低&#xff0c;由于要配合使用1.8.6&#xff0c;所以这里采用java后台运行sentinel1.8.6-jar的方式。 1、官网下载对应版本jar&#xff08;https://github.com/alibaba/Sentin…

MySQL间隙锁深入分析

概念 什么是间隙锁&#xff1f; MySQL的间隙锁&#xff08;gap lock&#xff09;是一种锁定相邻数据间隔的机制。 触发时机&#xff1f; 当使用SELECT…FOR UPDATE或UPDATE语句时&#xff0c;MySQL会获取一个范围锁&#xff0c;包括指定条件内的所有数据行&#xff0c;并且还…

rhcsa4 进程和SSH

tree命令。用于以树状结构显示目录和文件。通过运行 “tree” 命令可视化地查看文件系统中的目录结构。 tree / systemd是第一个系统进程&#xff08;pid1&#xff09;不启动&#xff0c;其他进程也没法启动&#xff0c; 用pstree查看进程树 我们可以看到所有进程都是syste…

设计模式之模板模式

文章目录 豆浆制作问题模板方法模式基本介绍模板方法模式原理类图对原理类图的说明-即(模板方法模式的角色及职责)模板方法模式解决豆浆制作问题模板方法模式的钩子方法模板方法模式的注意事项和细节 豆浆制作问题 编写制作豆浆的程序&#xff0c;说明如下: 制作豆浆的流程 选…

RocketMQ 消息传递模型

文章目录 0. 前言1. RocketMQ的消息传递模型1.1. 同步发送1.2. 异步发送1.3. 单向发送 2. RocketMQ的批量发送和消费2.1 批量发送2.2 批量消费2.3 Spring Boot集成RocketMQ官方starter 示例 3. 总结4. 参考文档5. 源码地址 0. 前言 RocketMQ 支持6种消息传递方式&#xff0c;我…

pyarmor 加密许可证的使用

一 pyarmor 许可证的用处 文档&#xff1a;5. 许可模式和许可证 — Pyarmor 8.3.6 文档 试用版本有如下的限制&#xff1a; 加密功能对脚本大小有限制&#xff0c;不能加密超过限制的大脚本。 混淆字符串功能在试用版中无法使用。 RFT 加密模式&#xff0c;BCC 加密模式在试…

解决java.io.IOException: Network error

解决java.io.IOException: Network error 解决java.io.IOException: Network error摘要引言正文1. 理解异常的根本原因2. 处理网络连接问题3. 处理连接超时4. 处理协议错误或不匹配5. 异常处理 总结参考资料 博主 默语带您 Go to New World. ✍ 个人主页—— 默语 的博客&#…

24.Xaml ListView控件-----显示数据

1.运行效果 2.运行源码 a.Xaml源码 <Window x:Class="testView.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.mic…

电子信息工程专业课复习知识点总结:(四)信号与系统、数字信号处理

这次我不具体把所有概念写出来了&#xff0c;只针对一些面试中经常提问的重点问题。 第一章 信号与系统基本概念 这里提出一个信号与系统这本书的大纲&#xff1a;这本书研究的就是信号与系统的关系。 一.信号是什么&#xff1f; ①信息是自然世界中一种表现形式&#xff0…

pkg 打包 nodejs

一、先全局安装pkg npm i -g pkg 二、下载打包所需的 node-v16.16.0-linux-x64 和 node-v16.16.0-win-x64 下载地址&#xff0c;里面选择你需要的版本 三、放到pkg的缓存目录 windows&#xff1a;C:\Users\whh\.pkg-cache\v3.4&#xff0c;&#xff08;把whh替换为你的电脑…

用冒泡排序完成库函数qsort的作用

Hello&#xff0c;今天分享的是我们用冒泡函数实现qsort&#xff0c;也就是快排&#xff0c;之前我们也讲过库函数qsort的使用方法&#xff0c;今天我们尝试用冒泡函数实现一下&#xff0c;当然我们也见过qsort&#xff0c;后面也会继续完善的。这几天我是破防大学生&#xff0…

MFC-GetAdaptersAddresses获取网卡信息

需要&#xff1a;#pragma comment(lib, "IPHLPAPI.lib") GetAdaptersAddresses函数参数说明 ULONG bufferSize 0;ULONG result ::GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, nullptr, &bufferSize);/*参数1&#xff1a;ULONG Famil…