当前位置: 首页 > news >正文

Springboot 对于数据库字段加密方案(此方案是对字符串处理的方案)

背景:在erp开发中,有些用户比较敏感数据库里的数据比较敏感,系统给用户部署后,公司也不想让任何人看到数据,所以就有了数据库字段加密方案。

技术 spring boot + mybatisplus 3.3.1

mybatisplus 实际提供了 字段加密方案
第一 他要钱
第二 他是在实体类上加注解 满足不了我们的需求
在这里插入图片描述

我们的整体需求是
用户可以自定义配置字段
在系统设置里 有个 字段加密 菜单

  1. 点击某个表单 然后显示这个表单所需要的字段
    2.勾选 某个字段 这个字段 就会激活 再提交 更新表单的时候 这个字段就会加密处理
    比如 系统管理 字段加密 他点击了请假表单 然后勾选 请假事由 字段加密
    然后 公司的员工再次提交表单的时候 请假事由 就会被加密 。

这个需求 用户再页面上操作 我们是不需要改代码的 。

如果利用实体类加注解方案 肯定满足不了 因为 每个用户加密的字段不一样,
鬼知道 加密注解要加在哪个实体类上

我们的系统 表单 和表单的字段 都定义在数据库里 所以 可以自由选择 表单和字段
这个根据各自系统自行修改

下面直接分享 加密的代码和思路 。原理我就不再说 ,用到的知识 自行百度即可

第一步 : 首先把用户勾选需要加密的 字段 缓存到redis 减少数据库查询
// 这段代码 就不分享了 自由编写
根据表名 获取 需要加密的字段

 List<String> stringList= BaseDataUtil.getFieldPassword(insertSql.getTableName());

第二步:
引入加密依赖

     <!--对数据库字段进行加密、脱敏--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.3</version></dependency>

第三步:
配置文件 配置
在这里插入图片描述

第四步 编写加密方案 利用的是 框加下的这个类 BaseTypeHandler
对于这个类的介绍 自行百度

自定义一个类 然后继承 这个类 BaseTypeHandler
重写 父类方法

package com.erp.init.handlers;import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import com.erp.init.utils.BaseDataUtil;
import org.apache.ibatis.logging.jdbc.PreparedStatementLogger;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;import java.lang.reflect.Proxy;
import java.nio.charset.StandardCharsets;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;/*** User: Json* <p>* Date: 2023/11/14**/
public class EncryptHandler extends BaseTypeHandler<String> {/*** 线上运行后勿修改,会影响已加密数据解密*/private static final byte[] KEYS = "shc987654321camp".getBytes(StandardCharsets.UTF_8);private static final String dataWithPrefix = "A:Json";/*** 设置参数*/@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {if (StringUtils.isEmpty(parameter)) {ps.setString(i, null);return;}// 获取动态代理类的 InvocationHandlerPreparedStatementLogger  handler =(PreparedStatementLogger) Proxy.getInvocationHandler(ps);PreparedStatement preparedStatement= handler.getPreparedStatement();MetaObject stmtMetaObj = SystemMetaObject.forObject(preparedStatement);String sql =  stmtMetaObj.getValue("sql").toString();//System.out.println("sql:"+sql);SqlResult updateSql= updateSql(sql);if(!ObjectUtils.isEmpty(updateSql)){if(updateSql.getColumnNames().size()>(i-1)){//            System.out.println("更新数据:"+updateSql);
//            System.out.println(i+"===>"+updateSql.getColumnNames().get(i-1));List<String> stringList= BaseDataUtil.getFieldPassword(updateSql.getTableName());if (!CollectionUtils.isEmpty(stringList) &&!CollectionUtils.isEmpty(updateSql.getColumnNames()) &&stringList.contains(updateSql.getColumnNames().get(i-1))) {AES aes = SecureUtil.aes(KEYS);String encrypt = aes.encryptHex(parameter);ps.setString(i,dataWithPrefix+ encrypt);} else {ps.setString(i, parameter);}}else{ps.setString(i, parameter);}}SqlResult insertSql=insetSQl(sql);if(!ObjectUtils.isEmpty(insertSql)){if(insertSql.getColumnNames().size()>(i-1)){//            System.out.println("新增数据:"+insertSql);
//            System.out.println(i+"===>"+insertSql.getColumnNames().get(i-1));List<String> stringList= BaseDataUtil.getFieldPassword(insertSql.getTableName());if (!CollectionUtils.isEmpty(stringList) &&!CollectionUtils.isEmpty(insertSql.getColumnNames()) &&stringList.contains(insertSql.getColumnNames().get(i-1))) {AES aes = SecureUtil.aes(KEYS);String encrypt = aes.encryptHex(parameter);ps.setString(i, dataWithPrefix+ encrypt);} else {ps.setString(i, parameter);}}else{ps.setString(i, parameter);}}if(ObjectUtils.isEmpty(insertSql) && ObjectUtils.isEmpty(updateSql) ){ps.setString(i, parameter);}}/*** 获取值*/@Overridepublic String getNullableResult(ResultSet rs, String columnName) throws SQLException {return decrypt(rs.getString(columnName),columnName,rs.getMetaData().getTableName(1));}/*** 获取值*/@Overridepublic String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {//  return null;return rs.getString(columnIndex);}/*** 获取值*/@Overridepublic String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {// return null;return cs.getString(columnIndex);}public String decrypt(String value,String columnName,String tableName) {// return  value;if (null == value) {return null;}List<String> stringList= BaseDataUtil.getFieldPassword(tableName);if(CollectionUtils.isEmpty(stringList)){return  value;}if (value.startsWith(dataWithPrefix) && stringList.contains(columnName)) {// 是新数据,去掉前缀String decryptedData = value.substring(dataWithPrefix.length());return SecureUtil.aes(KEYS).decryptStr(decryptedData);} else {return  value;}}public static SqlResult  updateSql(String sql) {// 假设你已经有了sql字符串// String sql = "UPDATE your_table SET column1 = value1, column2 = value2 WHERE condition";// 匹配UPDATE语句Pattern updatePattern = Pattern.compile("UPDATE\\s+([^\\s]+)\\s+SET\\s+([^\\s]+\\s*=\\s*[^,]+(,\\s*[^\\s]+\\s*=\\s*[^,]+)*)\\s+WHERE\\s+(.+)");Matcher updateMatcher = updatePattern.matcher(sql);// 如果是UPDATE语句if (updateMatcher.matches()) {String tableName = updateMatcher.group(1); // 获取表名String setClause = updateMatcher.group(2); // 获取SET子句// 提取更新的字段名List<String> columnNames = extractColumnNames(setClause);// 返回表名和字段名的信息return new SqlResult(tableName, columnNames);}return null;}private static List<String> extractColumnNames(String setClause) {List<String> columnNames = new ArrayList<>();String[] columns = setClause.split("\\s*,\\s*");for (String column : columns) {String[] parts = column.split("\\s*=\\s*");String columnName = parts[0].trim();columnNames.add(columnName);}return columnNames;}private static List<String> extractColumnNamesInsert(String columnsClause) {String[] columns = columnsClause.split("\\s*,\\s*");List<String> columnNames = new ArrayList<>();for (String column : columns) {columnNames.add(column.trim());}return columnNames;}public static  SqlResult insetSQl(String sql) {// 假设你已经有了sql字符串// String sql = "INSERT INTO your_table (column1, column2) VALUES (value1, value2)";// 匹配INSERT语句Pattern insertPattern = Pattern.compile("INSERT\\s+INTO\\s+([^\\s]+)\\s*\\(([^)]+)\\)\\s*VALUES\\s*\\(([^)]+)\\)");Matcher insertMatcher = insertPattern.matcher(sql);// 如果是INSERT语句if (insertMatcher.matches()) {String tableName = insertMatcher.group(1); // 获取表名String columnsClause = insertMatcher.group(2); // 获取列名的部分//  String valuesClause = insertMatcher.group(3); // 获取值的部分// 提取新增的字段名和对应的值List<String> columnNames = extractColumnNamesInsert(columnsClause);// 返回表名、字段名和对应值的信息return new SqlResult(tableName, columnNames);}return null;}}

第五步:
把这个类注册到配置文件中

package com.erp.init.mybatisplus;import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.erp.init.handlers.EncryptHandler;
import org.apache.ibatis.type.JdbcType;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** User: Json* <p>* Date: 2023/11/15**/
@Configuration
public class MyBatisConfig {@Beanpublic ConfigurationCustomizer configurationCustomizer() {return configuration -> {//String.class, JdbcType.VARCHAR  这个是 只对 字符串进行处理// int float 的加密 根据字符串再扩展一个类就行了 configuration.getTypeHandlerRegistry().register(String.class, JdbcType.VARCHAR, new EncryptHandler());// 注册其他类型处理器};}
}

这要编写后 每次 mybatisplus 调用 新增 和 更新 批量更新 批量操作 都会触发这里的代码
实现字段加解密

相关文章:

Springboot 对于数据库字段加密方案(此方案是对字符串处理的方案)

背景:在erp开发中&#xff0c;有些用户比较敏感数据库里的数据比较敏感&#xff0c;系统给用户部署后&#xff0c;公司也不想让任何人看到数据&#xff0c;所以就有了数据库字段加密方案。 技术 spring boot mybatisplus 3.3.1 mybatisplus 实际提供了 字段加密方案 第一 他…...

[C++]:8.C++ STL引入+string(介绍)

C STL引入string(介绍&#xff09; 一.STL引入&#xff1a;1.什么是STL2.什么是STL的版本&#xff1a;2-1&#xff1a;原始版本&#xff1a;2-2&#xff1a;P. J 版本&#xff1a;2-3&#xff1a;RW 版本&#xff1a;2-4&#xff1a;SGL版本&#xff1a; 3.STL 的六大组件&…...

C++基础从0到1入门编程(三)

系统学习C 方便自己日后复习&#xff0c;错误的地方希望积极指正 往期文章&#xff1a; C基础从0到1入门编程&#xff08;一&#xff09; C基础从0到1入门编程&#xff08;二&#xff09; 参考视频&#xff1a; 1.黑马程序员匠心之作|C教程从0到1入门编程,学习编程不再难 2.系统…...

[Jenkins] 物理机 安装 Jenkins

这里介绍Linux CentOS系统直接Yum 安装 Jenkins&#xff0c;不同系统之间类似&#xff0c;操作命令差异&#xff0c;如&#xff1a;Ubuntu用apt&#xff1b; 0、安装 Jenkins Jenkins是一个基于Java语言开发的持续构建工具平台&#xff0c;主要用于持续、自动的构建/测试你的软…...

设计模式 -- 适配器模式(Adapter Pattern)

适配器模式&#xff1a;属于结构型模式&#xff0c;结合了两个独立接口的功能&#xff0c;作为 两个不兼容的接口之间的桥梁 。 介绍 意图&#xff1a;将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。主要…...

Axios传值的几种方式

<body><script src"https://unpkg.com/axios/dist/axios.min.js"></script></body> axios基本使用 默认是get请求 注意&#xff1a;get请求无请求体&#xff0c;可以有body,但是不建议带 使用get方式进行无参请求 <script>axios(…...

git pull 报错 error object file is empty , The remote end hung up unexpectedly

报错原因分析&#xff1a;git pull的时候服务器在重启&#xff0c;导致git文件损坏 方法来源&#xff1a; 解决git错误: error object file is empty , The remote end hung up unexpectedly-CSDN博客 亲测有效 find .git/objects/ -type f -empty | xargs rm git fetch -p…...

手机数码类展示预约小程序效果如何

对于一家手机数码/电脑品牌来说&#xff0c;研发产品或衍生产品不少&#xff0c;通常会通过线上商城进行售卖。十年以来&#xff0c;流量成本逐渐增加&#xff0c;获客不易也难以寻找到合适的渠道&#xff0c;即使通过广告形式也因缺乏创意而耗时耗力&#xff0c;效果不佳。 同…...

图神经网络:消息传递算法

一、说明 图网络-GNN&#xff08;Graph Neural Networks&#xff09;是近几年研究的主题之一&#xff0c;虽不及深度神经网络那么火爆&#xff0c;但在一些领域&#xff0c;如分子化学方面是不得不依赖的理论。本文就一些典型意义的图神经网络消息传递展开阐述。 二、图网络简述…...

安全+Linux!IBM新一代大型机Z14全新发布

导读本周&#xff0c;以“架构 人机同行”为主题的IBM Systems创行者高峰论坛在北京召开&#xff0c;IBM全球及大中华区硬件系统部负责人&#xff0c;金融、医疗、制造等领域的企业、合作伙伴共与这一年度盛会&#xff0c;探讨认知时代下的基础架构技术趋势及IBM硬件系统业务的…...

Java中的局部变量和成员变量的区别

局部变量和成员变量的区别 区别1&#xff1a;代码中位置不同 成员变量&#xff1a;类中方法外定义的变量 局部变量&#xff1a;方法中定义的变量 代码块中定义的变量 区别2&#xff1a;代码的作用范围 成员变量&#xff1a;当前类的很多方法 局部变量&#xff1a;当前一…...

基于C++实现循环赛日程表(分治算法)

一、问题描叙 设有n2^k个运动员&#xff0c;要进行网球循环赛。现在要设计一个满足以下要求的比赛日程表 每个选手必须与其他n-1个选手各赛一场每个选手一天只能赛一次循环赛一共进行n-1天 二、问题分析 按此要求可将比赛日程表设计成n行n-1列的表&#xff0c;在表中第 i 行…...

基于uni-app的汽车租赁app的设计与实现

1.项目背景及意义 项目背景&#xff1a; 随着人们生活水平的提高&#xff0c;汽车租赁服务在城市中变得越来越普及。传统的租车方式存在一些问题&#xff0c;比如租车流程繁琐、费用不透明、选择有限等。因此&#xff0c;开发一款基于uni-app的汽车租赁app成为了满足用户需求…...

3.8-镜像的发布

如果我们想将image push到docker hub里面&#xff0c;那么我们的image的名字一定要是这种格式&#xff1a;docker hub id/imageName&#xff0c;例如&#xff1a;lvdapiaoliang/hello-docker docker hub个人账户设置地址&#xff1a; 在push之前要先登录&#xff1a; docker l…...

Navicat 基于 GaussDB 主备版的快速入门

Navicat Premium&#xff08;16.2.8 Windows版或以上&#xff09; 已支持对GaussDB 主备版的管理和开发功能。它不仅具备轻松、便捷的可视化数据查看和编辑功能&#xff0c;还提供强大的高阶功能&#xff08;如模型、结构同步、协同合作、数据迁移等&#xff09;&#xff0c;这…...

String的字符串拼接

java中 String a “123” “234”; String b “123”; String c b “234”; 其中a和c的区别是什么&#xff1f; a c 为什么为false 在Java中&#xff0c;字符串的处理特别是涉及到字符串常量和字符串变量的连接时&#xff0c;会涉及到字符串池&#xff08;String Pool&a…...

反渗透水处理成套设备有哪些

反渗透水处理成套设备主要包括反渗透装置、预处理系统、控制系统等部分。 反渗透装置&#xff1a;反渗透水处理设备的核心部分&#xff0c;由反渗透膜、压力容器、膜组件等组成。反渗透膜是一种高分子材料制成的半透膜&#xff0c;能够截留水中的溶解盐、有机物、细菌等杂质&a…...

DPC15 国产带有 SPI 接口的独立 CAN 控制器兼容替代MCP2551

DPC15是一款独立控制器局域网络&#xff08;Controller Area Network&#xff0c;CAN&#xff09;协议控制器&#xff0c;完全支持CAN V2.0B技术规范。该器件能发送和接收标准和扩展数据帧以及远程帧。 DPC15自带的两个验收屏蔽寄存器和六个验收滤波寄存器可以过滤掉不想要的报…...

【ELK01】ELK简介以及ElasticSearch安装、ES客户端工具-Head安装、报错问题整理

有一段时间没有更新这个专栏了,最近在用ELK相关的技术,今天开始写一下ELK的系列的内容,与大家共同学习 一、什么是ELK ELK 是elastic公司提供的一套完整的日志收集以及展示的解决方案,是三个产品的首字母缩写,分别是ElasticSearch、Logstash 和 Kibana。 1. E-ELASTICS…...

根据音频绘制频谱

根据音频链接绘制频谱图 封装 // 可以这样使用 也可以 import { AudioContext } from standardized-audio-context; const getAudioContext window.AudioContext ||window.webkitAudioContext ||window.mozAudioContext ||window.msAudioContext;const clearArr []export c…...

React hook之useRef

React useRef 详解 useRef 是 React 提供的一个 Hook&#xff0c;用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途&#xff0c;下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)

目录 1.TCP的连接管理机制&#xff08;1&#xff09;三次握手①握手过程②对握手过程的理解 &#xff08;2&#xff09;四次挥手&#xff08;3&#xff09;握手和挥手的触发&#xff08;4&#xff09;状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命

在华东塑料包装行业面临限塑令深度调整的背景下&#xff0c;江苏艾立泰以一场跨国资源接力的创新实践&#xff0c;重新定义了绿色供应链的边界。 跨国回收网络&#xff1a;废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点&#xff0c;将海外废弃包装箱通过标准…...

2021-03-15 iview一些问题

1.iview 在使用tree组件时&#xff0c;发现没有set类的方法&#xff0c;只有get&#xff0c;那么要改变tree值&#xff0c;只能遍历treeData&#xff0c;递归修改treeData的checked&#xff0c;发现无法更改&#xff0c;原因在于check模式下&#xff0c;子元素的勾选状态跟父节…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

力扣-35.搜索插入位置

题目描述 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...

十九、【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建

【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建 前言准备工作第一部分:回顾 Django 内置的 `User` 模型第二部分:设计并创建 `Role` 和 `UserProfile` 模型第三部分:创建 Serializers第四部分:创建 ViewSets第五部分:注册 API 路由第六部分:后端初步测…...

Java多线程实现之Runnable接口深度解析

Java多线程实现之Runnable接口深度解析 一、Runnable接口概述1.1 接口定义1.2 与Thread类的关系1.3 使用Runnable接口的优势 二、Runnable接口的基本实现方式2.1 传统方式实现Runnable接口2.2 使用匿名内部类实现Runnable接口2.3 使用Lambda表达式实现Runnable接口 三、Runnabl…...

【Redis】Redis从入门到实战:全面指南

Redis从入门到实战:全面指南 一、Redis简介 Redis(Remote Dictionary Server)是一个开源的、基于内存的键值存储系统,它可以用作数据库、缓存和消息代理。由Salvatore Sanfilippo于2009年开发,因其高性能、丰富的数据结构和广泛的语言支持而广受欢迎。 Redis核心特点:…...