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


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

    技术 spring boot + mybatisplus 3.3.1

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

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

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

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

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

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

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

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

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

    第二步:
    引入加密依赖

         <!--对数据库字段进行加密、脱敏-->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>5.8.3</version>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    第四步 编写加密方案 利用的是 框加下的这个类 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
     * 

    * 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"; /** * 设置参数 */ @Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { if (StringUtils.isEmpty(parameter)) { ps.setString(i, null); return; } // 获取动态代理类的 InvocationHandler PreparedStatementLogger 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); } } /** * 获取值 */ @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { return decrypt(rs.getString(columnName),columnName,rs.getMetaData().getTableName(1)); } /** * 获取值 */ @Override public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException { // return null; return rs.getString(columnIndex); } /** * 获取值 */ @Override public 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; } }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228

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

    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
     * 

    * Date: 2023/11/15 **/ @Configuration public class MyBatisConfig { @Bean public ConfigurationCustomizer configurationCustomizer() { return configuration -> { //String.class, JdbcType.VARCHAR 这个是 只对 字符串进行处理 // int float 的加密 根据字符串再扩展一个类就行了 configuration.getTypeHandlerRegistry().register(String.class, JdbcType.VARCHAR, new EncryptHandler()); // 注册其他类型处理器 }; } }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

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

  • 相关阅读:
    AWD常见防御加固手段
    笔试强训(二十四)
    从零开始配置 vim(12)——主题配置
    Redis 面试题
    Vue(贰)
    重磅 | 死磕 Elasticsearch 8.X 方法论认知清单(2022年国庆更新版)
    Model/ModelAndView
    Java(面试题准备(技术面)(数据库部分))
    QT5自定义下拉框为QTreeView类型(树形分上下级)的下拉框(QComboBox)(超详细步骤)
    如何有效管理产品生命周期(How to Effectively Manage a Product Lifecycle)
  • 原文地址:https://blog.csdn.net/Drug_/article/details/134492033