MyBatisPlus数据自动加解密存取和字段防篡改有效性校验码自动生成存储处理器

news2025/1/18 11:42:07

整体原理:用Mybatis拦截器拦截ResultSetHandler,做属性解密和完整性校奏。替换默认的ParameterHandler处理器。做属性加密存储和完整性加密存储。
代码结构如下:
在这里插入图片描述
各功能类解释:
1、EntityClassResolver:用于解析当前MapperStatament的Entity参数。
2、EntityValueHelper: 获取或设置Entity对象的属性值工具。
3、SecretConfigurationCustomizer:使得SecretMybatisXMLLanguageDriver生效的自定义配置。替换mybatisPlus默认的XMLLanguageDriver
4、SecretMybatisXMLLanguageDriver:使得SecretMybatisParameterHandler生效的自定义配置。替换mybatisPlus默认的MybatisParameterHandler
5、SecretDecryptInterceptor,拦截ResultSetHandler.handleResultSets,解密带SecretField字段的entity属性。
6、SecretField,标记需要加解密和完整性校验的字段注解。
7、SecretModel,标记该实体有需要加解密的字段。
8、SecretProvider,加解密码供应商。
9、SecretProviders,加解密供应商工具类。
10、SeretSecurityAutoConfiguration,总配置类,用于开启是否向Spring注册启用加密码组件。
SecretWrapper,加解密过程相关临时变量封装类。
SecretWrarpperEnhancer:低层次代码向高层次代码的entity对象设置一些加解密字段的扩展接口。
SecretWrapperEnhancers,扩展接口工具类。

几个关键类:

public class SecretConfigurationCustomizer implements ConfigurationCustomizer {

    @Override
    public void customize(Configuration configuration) {
        LanguageDriverRegistry languageRegistry = configuration.getLanguageRegistry();
        languageRegistry.setDefaultDriverClass(SecretMybatisXMLLanguageDriver.class);
    }
}
public class SecretMybatisXMLLanguageDriver extends XMLLanguageDriver {

    @Override
    public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject,
                                                   BoundSql boundSql) {
        /* 使用自定义 ParameterHandler */
        return new SecretMybatisParameterHandler(mappedStatement, parameterObject, boundSql);
    }
}

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

    private final static Map<String, Boolean> EXISTS_DECRYPT = new HashMap<>();
    private final static Map<String, SecretWrapper> NEED_DECRYPT_FIELDS = new HashMap<>();

    @Autowired
    private SecretProvider secretProvider;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取结果集的类型
        MappedStatement mappedStatement = resolveMappedStatement(invocation);
        String id = mappedStatement.getId();
        //
        Boolean needDecrypt = EXISTS_DECRYPT.get(id);
        if (null != needDecrypt && !needDecrypt) {
            return invocation.proceed();
        }
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        if (ZYListUtils.isEmptyList(resultMaps)) {
            EXISTS_DECRYPT.put(id, false);
            return invocation.proceed();
        }
        SecretWrapper secretWrapper = NEED_DECRYPT_FIELDS.get(id);
        if (null == secretWrapper) {
            Class<?> resultType = resultMaps.get(0).getType();
            secretWrapper = new SecretWrapper(resultType);
            if (secretWrapper.isEmpty()) {
                EXISTS_DECRYPT.put(id, false);
                return invocation.proceed();
            } else {
                EXISTS_DECRYPT.put(id, true);
                NEED_DECRYPT_FIELDS.put(id, secretWrapper);
            }
        }

        Object resultObject = invocation.proceed();
        if (null != resultObject && resultObject instanceof List) {
            List<?> list = (List<?>) resultObject;
            for (Object item : list) {
                this.doDecryptObjectValue(item, secretWrapper);
            }
        }
        return resultObject;
    }

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

    @Override
    public void setProperties(Properties properties) {

    }

    private MappedStatement resolveMappedStatement(Invocation invocation) {
        DefaultResultSetHandler defaultResultSetHandler = (DefaultResultSetHandler) invocation.getTarget();
        MetaObject metaObject = SystemMetaObject.forObject(defaultResultSetHandler);
        return (MappedStatement) metaObject.getValue("mappedStatement");
    }

    protected void doDecryptObjectValue(Object data, SecretWrapper secretWrapper) {
        // 解密
        List<String> decryptFields = secretWrapper.getDecryptFields();
        for (String field : decryptFields) {
            Object fieldValue = ZYBeanUtils.getProperty(data, field);
            if (ZYStrUtils.isNotNull(fieldValue)) {
                fieldValue = SecretProviders.decrypt(String.valueOf(fieldValue));
                ZYBeanUtils.setProperty(data, field, fieldValue);
            }
        }

        // 完整性校验
        List<String> signFields = secretWrapper.getSignFields();
        Map<String, String> signCodeFieldContainer = secretWrapper.getSignCodeFieldContainer();
        for (String signField : signFields) {
            Object fieldValue = ZYBeanUtils.getProperty(data, signField);
            if (ZYStrUtils.isNotNull(fieldValue)) {
                // 找到当前属性的签名
                String signCodeField = signCodeFieldContainer.get(signField);
                Object signCodeValue = ZYBeanUtils.getProperty(data, signCodeField);
                if (ZYStrUtils.isNotNull(signCodeValue)) {
                    // 校验数据完整性
                    boolean legal = SecretProviders.isLegal(String.valueOf(fieldValue), String.valueOf(signCodeValue));
                    if (!legal) {
                        throw new LocalException("非法数据" + fieldValue);
                    }
                }
            }
        }
    }
}
public class SecretMybatisParameterHandler extends DefaultParameterHandler {

    private final TypeHandlerRegistry typeHandlerRegistry;
    private final MappedStatement mappedStatement;
    private final Object parameterObject;
    private final BoundSql boundSql;
    private final Configuration configuration;

    public SecretMybatisParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        super(mappedStatement, processBatch(mappedStatement, parameterObject), boundSql);
        this.mappedStatement = mappedStatement;
        this.configuration = mappedStatement.getConfiguration();
        this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
        this.parameterObject = parameterObject;
        this.boundSql = boundSql;
    }

    /**
     * 批量(填充主键 ID)
     *
     * @param ms              MappedStatement
     * @param parameterObject 插入数据库对象
     * @return ignore
     */
    protected static Object processBatch(MappedStatement ms, Object parameterObject) {
        //检查 parameterObject
        if (null == parameterObject) {
            return null;
        }
        // 全局配置是否配置填充器
        MetaObjectHandler metaObjectHandler = GlobalConfigUtils.getMetaObjectHandler(ms.getConfiguration());
        boolean isFill = false;
        boolean isInsert = false;
        /* 只处理插入或更新操作 */
        if (ms.getSqlCommandType() == SqlCommandType.INSERT) {
            isFill = true;
            isInsert = true;
        } else if (ms.getSqlCommandType() == SqlCommandType.UPDATE &&
                metaObjectHandler != null && metaObjectHandler.openUpdateFill()) {
            isFill = true;
        }
        if (isFill) {
            Collection<Object> parameters = getParameters(parameterObject);
            if (null != parameters) {
                List<Object> objList = new ArrayList<>();
                for (Object parameter : parameters) {
                    TableInfo tableInfo = TableInfoHelper.getTableInfo(parameter.getClass());
                    if (null != tableInfo) {
                        objList.add(populateKeys(metaObjectHandler, tableInfo, ms, parameter, isInsert));
                    } else {
                        /*
                         * 非表映射类不处理
                         */
                        objList.add(parameter);
                    }
                }
                return objList;
            } else {
                TableInfo tableInfo = null;
                if (parameterObject instanceof Map) {
                    Map map = (Map) parameterObject;
                    if (map.containsKey(Constants.ENTITY)) {
                        Object et = map.get(Constants.ENTITY);
                        if (et != null) {
                            if (et instanceof Map) {
                                Map realEtMap = (Map) et;
                                if (realEtMap.containsKey("MP_OPTLOCK_ET_ORIGINAL")) {
                                    //refer to OptimisticLockerInterceptor.MP_OPTLOCK_ET_ORIGINAL
                                    tableInfo = TableInfoHelper.getTableInfo(realEtMap.get("MP_OPTLOCK_ET_ORIGINAL").getClass());
                                }
                            } else {
                                tableInfo = TableInfoHelper.getTableInfo(et.getClass());
                            }
                        }
                    }
                } else {
                    tableInfo = TableInfoHelper.getTableInfo(parameterObject.getClass());
                }
                return populateKeys(metaObjectHandler, tableInfo, ms, parameterObject, isInsert);
            }
        }
        return parameterObject;
    }

    /**
     * 处理正常批量插入逻辑
     * <p>
     * org.apache.ibatis.session.defaults.DefaultSqlSession$StrictMap 该类方法
     * wrapCollection 实现 StrictMap 封装逻辑
     * </p>
     *
     * @param parameter 插入数据库对象
     * @return
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    protected static Collection<Object> getParameters(Object parameter) {
        Collection<Object> parameters = null;
        if (parameter instanceof Collection) {
            parameters = (Collection) parameter;
        } else if (parameter instanceof Map) {
            Map parameterMap = (Map) parameter;
            if (parameterMap.containsKey("collection")) {
                parameters = (Collection) parameterMap.get("collection");
            } else if (parameterMap.containsKey("list")) {
                parameters = (List) parameterMap.get("list");
            } else if (parameterMap.containsKey("array")) {
                parameters = Arrays.asList((Object[]) parameterMap.get("array"));
            }
        }
        return parameters;
    }

    /**
     * 自定义元对象填充控制器
     *
     * @param metaObjectHandler 元数据填充处理器
     * @param tableInfo         数据库表反射信息
     * @param ms                MappedStatement
     * @param parameterObject   插入数据库对象
     * @return Object
     */
    protected static Object populateKeys(MetaObjectHandler metaObjectHandler, TableInfo tableInfo,
                                         MappedStatement ms, Object parameterObject, boolean isInsert) {
        if (null == tableInfo) {
            /* 不处理 */
            return parameterObject;
        }
        /* 自定义元对象填充控制器 */
        MetaObject metaObject = ms.getConfiguration().newMetaObject(parameterObject);
        // 填充主键
        if (isInsert && !StringUtils.isEmpty(tableInfo.getKeyProperty())
                && null != tableInfo.getIdType() && tableInfo.getIdType().getKey() >= 3) {
            Object idValue = metaObject.getValue(tableInfo.getKeyProperty());
            /* 自定义 ID */
            if (StringUtils.checkValNull(idValue)) {
                if (tableInfo.getIdType() == IdType.ID_WORKER) {
                    metaObject.setValue(tableInfo.getKeyProperty(), IdWorker.getId());
                } else if (tableInfo.getIdType() == IdType.ID_WORKER_STR) {
                    metaObject.setValue(tableInfo.getKeyProperty(), IdWorker.getIdStr());
                } else if (tableInfo.getIdType() == IdType.UUID) {
                    metaObject.setValue(tableInfo.getKeyProperty(), IdWorker.get32UUID());
                }
            }
        }

        doCompleteSignCode(metaObject, tableInfo);

        if (metaObjectHandler != null) {
            if (isInsert && metaObjectHandler.openInsertFill()) {
                // 插入填充
                metaObjectHandler.insertFill(metaObject);
            } else if (!isInsert) {
                // 更新填充
                metaObjectHandler.updateFill(metaObject);
            }
        }
        return metaObject.getOriginalObject();
    }

    // 可提前设置完整性signCode的值,所放提前设置
    private static void doCompleteSignCode(MetaObject metaObject, TableInfo tableInfo) {
        Class<?> clazz = tableInfo.getClazz();

        SecretWrapper secretWrapper = new SecretWrapper(clazz);
        if (secretWrapper.isEmpty()) {
            return;
        }
        Map<String, String> signContentContainer = secretWrapper.getSignContentContainer();
        signContentContainer.forEach((signCodeField, signContentField) -> {
            setSignCodeValue(metaObject, signCodeField, signContentField);
        });

    }

    private static void setSignCodeValue(MetaObject metaObject, String signCodeField, String signContentField) {
        if (ZYStrUtils.isNull(signContentField)) {
            return;
        }
        Object signContent = EntityValueHelper.getProperties(metaObject, signContentField);
        if (ZYStrUtils.isNull(signContent)) {
            return;
        }
        // 用完整性数据字段内容加密
        String signCode = SecretProviders.genLegalSign(String.valueOf(signContent));
        if (ZYStrUtils.isNotNull(signCode)) {
            EntityValueHelper.setProperties(metaObject, signCodeField, signCode);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (ZYListUtils.isEmptyList(parameterMappings)) {
            return;
        }

        for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            if (parameterMapping.getMode() == ParameterMode.OUT) {
                continue;
            }
            Object value;
            String propertyName = parameterMapping.getProperty();
            if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
                value = boundSql.getAdditionalParameter(propertyName);
            } else if (parameterObject == null) {
                value = null;
            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                value = parameterObject;
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                value = metaObject.getValue(propertyName);
                // 不能破坏对象原来的值,所以放这处理最终的设置sql数据
                value = doEncryptIfNecessary(metaObject, propertyName, value);
            }
            TypeHandler typeHandler = parameterMapping.getTypeHandler();
            JdbcType jdbcType = parameterMapping.getJdbcType();
            if (value == null && jdbcType == null) {
                jdbcType = configuration.getJdbcTypeForNull();
            }
            try {
                typeHandler.setParameter(ps, i + 1, value, jdbcType);
            } catch (TypeException | SQLException e) {
                throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
            }
        }
    }

    // 解密对象
    private Object doEncryptIfNecessary(MetaObject metaObject, String propertyName, Object value) {
        if (!isNecessarySecret(metaObject, value)) {
            return value;
        }
        Class<?> moduleClass = EntityClassResolver.resolveClass(mappedStatement, parameterObject);
        if (null == moduleClass) {
            return value;
        }
        SecretWrapper secretWrapper = new SecretWrapper(moduleClass);
        if (secretWrapper.isEmpty()) {
            return value;
        }

        PropertyTokenizer propertyTokenizer = new PropertyTokenizer(propertyName);
        // 加个密
        SecretModel secretField = secretWrapper.getSecretField(propertyTokenizer);
        if (null != secretField && secretField.isNeedEncrypt()) {
            return SecretProviders.encrypt(String.valueOf(value));
        }
        return value;
    }

    private boolean isNecessarySecret(MetaObject metaObject, Object value) {
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
        // 不是添加或修改
        if (!sqlCommandType.equals(SqlCommandType.INSERT) && !sqlCommandType.equals(SqlCommandType.UPDATE)) {
            return false;
        }

        if (ZYStrUtils.isNull(value)) {
            return false;
        }
        // 只支持处理string类型
        return value instanceof String;

    }
}

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

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

相关文章

Spring创建Ajax和接受Ajax数据-spring20

建一个AJAX.jsp页面 发送Ajax请求一般用jQuery 引入jQuery 引入文件&#xff1a; 弄一个请求 获得集合参数第二种应用场景 requestBody 的意思请求体 为什么找不到JQuery文件&#xff0c;原因是&#xff1a;前端控制器的配置&#xff1a;缺省&#xff0c;客户端发送请求&…

postgresql 数据库 面试题整理

postgresql 数据库 面试题整理 文章目录 postgresql 数据库 面试题整理前言pg数据库的特点&#xff1a;pg的优点pg的核心优势pg数据库的一些缺点PostgreSQL支持的各种数据类型pg的模式pg的多版本并发控制pg多版本并发控制&#xff08;MVCC&#xff09;介绍pg如何提供安全性Post…

vue3+vite配置 unplugin-vue-component 找不到 Vant 组件的问题

使用 vue3 vite Vant 搭建移动端项目&#xff0c;为了避免全量引入 vant 导致打包体积过大&#xff0c;又不想一个一个组件手动导入&#xff0c;所以就选择了 vant 官方推荐的方法&#xff0c;使用 unplugin-vue-components 插件自动引入组件&#xff0c;并按需引入组件的样式…

需求条目化与自动估算强强联合 助力软件估算自动化

痛点&#xff1a; 需求是产品的源头&#xff0c;是项目规模估算的基石。而传统的软件规模估算是由项目成员手工进行&#xff0c;对人员能力、经验、方法都有一定的要求&#xff0c;但是效果不好而且耗时费力&#xff0c;不能保持规模估算的一致性。 而导致这些问题的原因&#…

A plugin must either be a function or an object with an “install“ function

前言&#xff1a; 最近一个项目使用了vue3tsvite模型框架&#xff0c;在框架的基础之上进行的开发&#xff0c;开发过程中遇到一个问题&#xff0c;控制台有提示信息&#xff0c;如下图&#xff1a; 小小一行黄字&#xff0c;恶心人呀。。。 这话的意思大概是&#xff1a;插件…

中国人民大学与加拿大女王大学金融硕士--中外合作办学硕士领域的宝藏项目

大多数读研人都知道&#xff0c;从办学方式来看&#xff0c;中外合作办学硕士可以分为两种&#xff1a; 一是双学位教育项目&#xff0c;颁发的是外方高校硕士学位证书&#xff0c;同时还可以申请国内高校的硕士学位证书&#xff0c;同时教育部中留服还可以做学历学位认证。 二…

有哪些屏幕录制软件?这几种录屏工具了解一下

有哪些好用的屏幕录制软件呢&#xff1f;如果我们需要教别人如何使用某个软件或者如何完成某个任务&#xff0c;录屏也是非常有用的。可以通过录屏记录下整个过程&#xff0c;然后用于制作教程视频。也有小伙伴在打游戏时&#xff0c;遇到精彩操作有录屏的习惯&#xff0c;那么…

一文解惑mybatis中的#{}和${}

目录 基本概述 #{}的基本使用 ${}的基本使用 ${}使用情况 sql排序asc|desc 拼接表名 批量删除 模糊查询 基本概述 #{}&#xff1a;先编译sql语句&#xff0c;再给占位符传值&#xff0c;底层是PreparedStatement实现。可以防止sql注入&#xff0c;比较常用。 ${}&…

在Windows下安装Anaconda平台

Anaconda介绍 安装Python的方法有很多&#xff0c;其中利用Anaconda来安装&#xff0c;是最为安全和便捷的方法之一。在Python中安装类库&#xff0c;各个类库之间可能存在相互依赖、版本冲突等问题。为了解决这个问题&#xff0c;Python社区提供了方便的软件包管理工具&#…

css背景毛玻璃效果

一、结论&#xff1a;通过 css 的 backdrop-filter 属性设置滤镜函数 blur 一般会是有 背景色、透明度 的容器&#xff0c;如&#xff1a; /* 宽高等其他设置这里省略没写 */ background:rgba(3, 87, 255, 0.3); backdrop-filter: blur(10px);二、backdrop-filter 的其他用法…

ffmpeg学习之音频解码数据

音频数据经过解码后会被保存为&#xff0c;pcm数据格式。而对应的处理流程如下所示。 avcodec_find_encoder() /*** 查找具有匹配编解码器ID的已注册编码器.** param id AVCodecID of the requested encoder* return An encoder if one was found, NULL otherwise.*/ const A…

C# 移除链表元素

203 移除链表元素 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val 6 输出&#xff1a;[1,2,3,4,5] 示例 2&#x…

机器学习(14)--XGBoost

目录 一、概述 二、CART、GB、GBDT 1、CART 2、BT&#xff08;Boosting Tree提升树&#xff09; 3、GBDT&#xff08;梯度提升树&#xff09; 4、GBDT在sklearn中的损失函数 三、Sklearn中的GBDT 1、加载模块 2、划分数据集 3、建模 4、与随机森林和线性回归对比 5…

Redis可视化工具 - Another Redis Desktop Manager 安装与使用详细步骤

一、下载安装 Another Redis Desktop Manager AnotherRedisDesktopManager 发行版 - Gitee.com&#xff08;gitee&#xff09; 2. 安装 以管理员身份运行下载的安装包 选择是为所有用户还是当前用户安装&#xff0c;按需选择 选择安装位置&#xff0c;点击安装进行安装 安装…

uni-app实现emoj表情包发送(nvue版)

uni-app实现表情包发送&#xff0c; vue实现思路直接使用grideview网格布局加载emoj表情包即可实现&#xff0c;很简单&#xff0c;但是nvue稍微复杂&#xff0c;这里采用的方案是nvue提供的组件list 看效果 代码 <template><view style"margin-right: 10rpx;m…

跨项目实时通信——Broadcast Channel

一、背景 在日常开发中&#xff0c;肯定会遇到一些需要跨项目实时通信的场景。比如在浏览器中新开两个tab页面&#xff0c;A页面发送消息后&#xff0c;B页面实时监听并触发某些动作。类似的需求有很多&#xff0c;比如实时共享状态等等。那么跨项目/页面/浏览器tab的情况下如…

7.17~7.18

当端口冲突&#xff0c;可以查看&#xff1a;cmd然后输入以下命令 URL包括协议&#xff0c;域名&#xff0c;端口号&#xff0c;文件名&#xff1a; public class netProgramme {//定位一个节点public static void main(String[]args) throws UnknownHostException, Malformed…

flutter开发实战-svga播放svgaplayer_flutter直播礼物特效等效果使用

flutter开发实战-svga播放svgaplayer_flutter直播礼物特效等效果使用 最近开发过程中用到了SVGA进行播放动画&#xff0c;这里记录一下svgaplayer_flutter使用过程。svga可以做一些非常精美的动画&#xff0c;包括直播的刷礼物(火箭、跑车特效动画)等等。 效果图如下 一、SVG…

ACL2023论文-系列1

文章目录 Prompt——1.Generated Knowledge Prompting for Commonsense Reasoning核心论文贡献方法效果的影响因素方法实现 Contrastive learning——A Contrastive Framework for Learning Sentence Representations from Pairwise and Triple-wise Perspective in Angular Sp…

【深度学习笔记】梯度消失与梯度爆炸

本专栏是网易云课堂人工智能课程《神经网络与深度学习》的学习笔记&#xff0c;视频由网易云课堂与 deeplearning.ai 联合出品&#xff0c;主讲人是吴恩达 Andrew Ng 教授。感兴趣的网友可以观看网易云课堂的视频进行深入学习&#xff0c;视频的链接如下&#xff1a; 神经网络和…