mybatis用拦截器实现字段加解密

news2024/11/20 7:27:31

前言

根据公司业务需要,灵活对客户敏感信息进行加解密,这里采用mybatis拦截器进行简单实现个demo。

拦截器的使用

// 执行
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
// 请求参数处理
ParameterHandler (getParameterObject, setParameters)
// 返回结果集处理
ResultSetHandler (handleResultSets, handleOutputParameters)
// SQL语句构建
StatementHandler (prepare, parameterize, batch, update, query)

我们要实现数据加密,进入数据库的字段不能是真实的数据,但是返回来的数据要真实可用,所以我们需要针对 Parameter 和 ResultSet 两种类型处理,同时为了更灵活的使用,我们需要自定义注解。

/**
 *需要加解密的字段注解
**/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Encryption {
    String encryptionType() default "";
}

编写一下加解密算法(随便找的)

**
 * @desc: AES对称加密,对明文进行加密、解密处理
 * @author:
 * @createTime: 20231014 上午9:54:52
 * @version: v0.0.1
 */
public class AESUtil {
    private static final String KEY_ALGORITHM = "AES";
    private static final String CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";


    /**
     * @desc: AES对称-加密操作
     * @version: v0.0.1
     * @param keyStr 进行了Base64编码的秘钥
     * @param data 需要进行加密的原文
     * @return String 数据密文,加密后的数据,进行了Base64的编码
     */
    public static String encrypt(String keyStr, String data) throws Exception {
        // 转换密钥
        Key key = new SecretKeySpec(Base64.getDecoder().decode(keyStr), KEY_ALGORITHM);
        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        // 加密
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] result = cipher.doFinal(data.getBytes());
        return Base64.getEncoder().encodeToString(result);
    }
    /**
     * @desc: AES对称-解密操作
     * @version: v0.0.1
     * @param keyStr 进行了Base64编码的秘钥
     * @param data 需要解密的数据<span style="color:red;">(数据必须是通过AES进行加密后,对加密数据Base64编码的数据)</span>
     * @return String 返回解密后的原文
     */
    public static String decrypt(String keyStr, String data) throws Exception {
        // 转换密钥
        Key key = new SecretKeySpec(Base64.getDecoder().decode(keyStr), KEY_ALGORITHM);
        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        // 解密
        cipher.init(Cipher.DECRYPT_MODE, key);
        byte[] result = cipher.doFinal(Base64.getDecoder().decode(data));
        return new String(result);
    }


    /**
     * @desc: 生成AES的秘钥,秘钥进行了Base64编码的字符串
     * @version: v0.0.1
     * @return String 对生成的秘钥进行了Base64编码的字符串
     */
    public static String keyGenerate() throws Exception {
        // 生成密钥
        KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
        keyGenerator.init(new SecureRandom());
        SecretKey secretKey = keyGenerator.generateKey();
        byte[] keyBytes = secretKey.getEncoded();
        return Base64.getEncoder().encodeToString(keyBytes);
    }
    public static void main(String[] args) throws Exception {
        System.out.println(
                keyGenerate()
        );

    }
}

编写一下加解密接口

/**
 * 加解密处理接口
 */
public interface CipherHandler {

    /**
     * 加密
     * @param data 需要加密的数据
     * @return 加密结果
     */
   String encrypt(String data) throws Exception;

    /**
     * 解密
     * @param data 需要加密的数据
     * @return 解密结果
     */
    String decrypt(String data) throws Exception;

}

public class AEScipher implements CipherHandler{
    private final static String keyStr="glRwFSBcKzppYVQiwT8M/Q==";

    @Override
    public String encrypt(String data) throws Exception {
        return AESUtil.encrypt(this.keyStr,data);
    }

    @Override
    public String decrypt(String data) throws Exception {
        return AESUtil.decrypt(this.keyStr,data);
    }

    public static CipherHandler getAEScipher(){
        return new AEScipher();
    }
}

接下来写一下加解密的拦截器


@Intercepts({
        @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
})
@Component
@Slf4j
public class ParameterInterceptor implements Interceptor {

    private CipherHandler cipherHandler = AEScipher.getAEScipher();
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        DefaultParameterHandler parameterHandler = (DefaultParameterHandler) invocation.getTarget();
        // 获取参数对像,即 mapper 中 paramsType 的实例
        Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
        parameterField.setAccessible(true);
        // 取出实例
        Object parameterObject = parameterField.get(parameterHandler);
        try {
            // 搜索该方法中是否有需要加密的字段
            List<Field> fieldList = searchParamAnnotation(parameterHandler);
            //加密
            if(!CommonHelper.isEmpty(fieldList)){
                dealParamEncrypt(fieldList,parameterObject);
            }
            parameterField.set(parameterHandler, parameterObject);
            PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0];
            parameterHandler.setParameters(ps);

        } catch (Exception e) {
            log.info(e.getMessage());
        }
        return invocation.proceed();
    }


    /**
     * 查找需要需要加密字段
     * @param parameterHandler
     * @return
     * @throws Exception
     */
    private List<Field> searchParamAnnotation(ParameterHandler parameterHandler) throws Exception {
        Class<DefaultParameterHandler> handlerClass = DefaultParameterHandler.class;
        Field mappedStatementFiled = handlerClass.getDeclaredField("mappedStatement");
        mappedStatementFiled.setAccessible(true);
        MappedStatement mappedStatement = (MappedStatement) mappedStatementFiled.get(parameterHandler);
        String methodName = mappedStatement.getId();
        // 获取Mapper类对象
        Class<?> mapperClass = Class.forName(methodName.substring(0, methodName.lastIndexOf('.')));
        methodName = methodName.substring(methodName.lastIndexOf('.') + 1);
        Method[] methods = mapperClass.getDeclaredMethods();
        Method method = null;
        for (Method m : methods) {
            if (m.getName().equals(methodName)) {
                method = m;
                break;
            }
        }
        List<Field> fieldList = new ArrayList<>();
        if (method != null) {
            Annotation[][] pa = method.getParameterAnnotations();
            Parameter[] parameters = method.getParameters();
            for (int i = 0; i < pa.length; i++) {
                Parameter parameter = parameters[i];
                String typeName = parameter.getParameterizedType().getTypeName();
                // 去除泛型导致的ClassNotFoundException
                Class<?> parameterClass = Class.forName(typeName.contains("<") ? typeName.substring(0, typeName.indexOf("<")) : typeName);
                Field[] declaredFields = parameterClass.getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    Annotation annotation = declaredField.getAnnotation(Encryption.class);
                    if(!CommonHelper.isEmpty(annotation))fieldList.add(declaredField);
                }
            }
        }
        return fieldList;
    }

    /**
     * 处理加密类
     * @param fields
     * @param parameterObject
     * @throws Exception
     */
    private void dealParamEncrypt(List<Field> fields,Object parameterObject) throws Exception {
        fields.forEach(declaredField->{
            declaredField.setAccessible(true);
            Object o = null;
            try {
                o = declaredField.get(parameterObject);
                declaredField.set(parameterObject,cipherHandler.encrypt(o.toString()));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });

    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target,this);
    }

    @Override
    public void setProperties(Properties properties) {

    }

}

查询结果解密


@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
@Component
public class ResultSetInterceptor implements Interceptor {

    private CipherHandler cipherHandler = AEScipher.getAEScipher();
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 取出查询的结果
        Object resultObject = invocation.proceed();
        if (Objects.isNull(resultObject)) {
            return null;
        }
        // 基于selectList
        if (resultObject instanceof List<?>) {
            List<?> resultList = (List<?>) resultObject;
            if (!CommonHelper.isEmpty(resultList)) {
                for (Object obj : resultList) {
                    toDecrypt(obj);
                }
            }
        } else {
            toDecrypt(resultObject);
        }
        return resultObject;
    }

    private void toDecrypt(Object object) throws Exception {
        Class<?> objectClass = object.getClass();
        Field[] declaredFields = objectClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            Annotation annotation = declaredField.getAnnotation(Encryption.class);
            if(!CommonHelper.isEmpty(annotation)){
                declaredField.setAccessible(true);
                declaredField.set(object,cipherHandler.decrypt(declaredField.get(object).toString()));
            }
        }
    }

    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o,this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

判空工具类

public class CommonHelper {

    public static boolean isEmpty(Object o){
        if(o==null)return true;
        if(o instanceof String){
          return  ((String) o).isEmpty();
        }else if(o instanceof List){
            return CollectionUtils.isEmpty((List) o) || ((List<?>) o).size()==0 ;
        }else if(o instanceof Map){
            return CollectionUtils.isEmpty((Map) o) || ((Map<?,?>) o).size()==0 ;
        }else {
            return Objects.isNull(o);
        }
    }
}

测试结果:
插入数据
在这里插入图片描述
在这里插入图片描述

查询数据:
在这里插入图片描述

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

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

相关文章

基于蛾群优化的BP神经网络(分类应用) - 附代码

基于蛾群优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于蛾群优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.蛾群优化BP神经网络3.1 BP神经网络参数设置3.2 蛾群算法应用 4.测试结果&#xff1a;5.M…

常用的数字格式代码

文章目录 数值占位符文本占位符 两类占位符: 数值占位符, 文本占位符. 数值占位符 有三种&#xff1a;0&#xff0c;#&#xff0c;&#xff1f; 0 是强制的占位符。 文本占位符 文本占位符只有一个&#xff1a; : 作用于文本的占位符&#xff0c;可以用英文引号" &quo…

DirectX绘制流水线

使用DirectX可以让在Windows平台上运行的游戏或多媒体程序获得更高的执行效率&#xff0c;掌握DirectX的基本概念和技术是虚拟现实技术、计算机仿真和3D游戏程序开发的基础。 DirectX概述 DirectX是微软的一个多媒体应用编程接口(API)工具包&#xff0c;用于为Windows操作系统…

Qt QMovie和QLabel配合播放GIF表情包

文章目录 效果演示main函数创建MoviePlayer对象头文件movieplayer.h源文件movieplayer.cpp代码解释在Qt框架中,QMovie是用于处理动画和视频的类。所有源码已在本篇文章公布。 效果演示 main函数创建MoviePlayer对象 #include <QApplication>#include "movie

JAVAEE初阶相关内容第十三弹--文件操作 IO

写在前 终于完成了&#xff01;&#xff01;&#xff01;&#xff01;内容不多就是本人太拖拉&#xff01; 这里主要介绍文件input&#xff0c;output操作。File类&#xff0c;流对象&#xff08;分为字节流、字符流&#xff09; 需要掌握每个流对象的使用方式&#xff1a;打…

MySQL jdbc,事务,连接池

​​​ 3-MySQL jdbc,事务,连接 1 jdbc 1.1 jdbc概述# JDBC&#xff08;Java DataBase Connectivity,java数据库连接技术&#xff09;是一种用于执行SQL语句的Java API。 JDBC是Java访问数据库的标准规范&#xff0c;可以为不同的关系型数据库提供统一访问&#xff0c;它由一…

php对接微信支付简要流程?面试时你会描述吗?

一、微信支付申请&#xff1a;微信公众号平台-->功能中找到微信支付-->申请接入 1.如果没有微信支付商会号&#xff0c;需要进行申请 提交营业执照、身份证、银行账户 2.如果有微信支付商会号 可进行直接关联 登录微信商户平台—产品中心—APPID授权管理—新增授权申…

视频编辑软件 Premiere Pro 2024 macv24.0中文版 (pr2024)

Premiere Pro 2024 mac编辑任何现代格式的素材&#xff0c;从8K到虚拟现实。广泛的原生文件支持和简单的代理工作流程可以轻松使用您的媒体&#xff0c;即使在移动工作站上也是如此。提供针对任何屏幕或平台优化的内容比以往任何时候都快。 Premiere Pro 2024 Mac版软件介绍 视…

深度强化学习 第 2 章 蒙特卡洛

2.1随机变量 强化学习中会经常用到两个概念&#xff1a; 随机变量、 观测值。 本书用大写字母表示随机变量&#xff0c;小写字母表示观测值&#xff0c;避免造成混淆。 下面我们定义概率质量函数&#xff08;probability mass function&#xff0c;缩写 PMF&#xff09;和概率…

SpringBoot面试题5:SpringBoot Starter的工作原理是什么?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:SpringBoot Starter的工作原理是什么? Spring Boot Starter 是一种便捷的方式来为 Spring Boot 应用程序引入一组特定功能的依赖项。它简化了项目…

【Java并发】聊聊LongAdder应用场景及其原理

应用场景 我们知道在实际的应用场景中&#xff0c;可能会对某个商品进行浏览次数进行迭代&#xff0c;或者抖音视频的点击&#xff0c;那么如何高效记录呢&#xff0c;首先如果是使用普通的num 进行多线程操作的话&#xff0c;那么一定会带来数据一致性问题&#xff0c;所以一…

【Unity基础】6.动画状态机

【Unity基础】6.动画状态机 大家好&#xff0c;我是Lampard~~ 欢迎来到Unity基础系列博客&#xff0c;所学知识来自B站阿发老师~感谢 &#xff08;一&#xff09;Animator Controller组件 &#xff08;1&#xff09;创建组件 Animator Controller组件是unity用于控制管…

【细读经典】delay model and timing analysis

Technology-Dependent LogicOptimization, part 1 序言 如图所示是现代工业流程中对于一个高层次的抽象描述如何到最后的芯片的流程图&#xff0c;其中逻辑综合作为一个非常重要的部分&#xff0c;主要被分为两个阶段&#xff1a; 工艺无关的优化(technology-independent opt…

shell脚本学习笔记03(小滴课堂)

在shell脚本中&#xff0c;表示变量除了可以使用$a(a是一个变量)&#xff0c;还可以使用${a} 那这两种表示方式有什么区别么&#xff1f; 花括号可以和其它字符或者字母区分开来。 >追加内容 我们发现使用>会把原来的内容覆盖。 我们使用>>就不会覆盖了&#xff…

MAYA教程之模型的UV拆分与材质介绍

什么是UV 模型制作完成后&#xff0c;需要给模型进行贴图&#xff0c;就需要用到UV功能 UV编译器介绍 打开UI编译器 主菜单有一个 UV->UV编译器&#xff0c;可以点击打开 创建一个模型&#xff0c;可以看到模型默认的UV UV编译器功能使用 UV模式的选择 在UV编译器中…

从裸机启动开始运行一个C++程序(八)

前序文章请看&#xff1a; 从裸机启动开始运行一个C程序&#xff08;七&#xff09; 从裸机启动开始运行一个C程序&#xff08;六&#xff09; 从裸机启动开始运行一个C程序&#xff08;五&#xff09; 从裸机启动开始运行一个C程序&#xff08;四&#xff09; 从裸机启动开始运…

【C++】C++11 ——lambda表达式

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C学习 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【C】C11…

阿里云香港云服务器公网带宽价格表及测试IP地址

阿里云服务器香港地域公网带宽价格表&#xff0c;1M带宽价格是30.0元/月&#xff0c;按使用流量1GB价格是1.0元&#xff0c;阿里云香港服务器测试IP地址&#xff1a;47.75.18.101&#xff0c;阿里云百科aliyunbaike.com来详细说下阿里云香港服务器1M带宽、5M带宽、6M带宽、10M带…

民宿酒店订房房态商城小程序的作用是什么

外出旅游出差&#xff0c;酒店民宿总是很好的选择&#xff0c;随着经济复苏&#xff0c;各地旅游及外出办公人次增多&#xff0c;酒店成绩随之增加&#xff0c;市场呈现多品牌酒店经营形式。 区别于以前&#xff0c;如今互联网深入各个行业&#xff0c;酒店经营也面临着困境。…

删除字符串特定的字符(fF)C语言

代码&#xff1a; #include <stdio.h> void funDel(char *str) {int i, j;for (i j 0; str[i] ! \0; i)if (str[i] ! f && str[i] ! F)str[j] str[i];str[j] \0; }int main() {char str[100];printf("请输入一个字符串&#xff1a;");gets(str);pr…