什么是脱敏
如果你有申请过一些软件资质,应该会被要求敏感数据进行加密,比如密码不能明文,用户的手机号,身份证信息,银行卡号信息,等等都需要以加密的形式放在数据库中,防止因系统漏洞导致被拖库,黑客直接就能拿到这些信息,给用户的人身财产安全带来严重的隐患。
脱敏实际就是加密,这边分为两种类型
- 不可逆 如密码这种直接采用不可逆的加密方式
- 可逆的 如手机号,身份证,银行卡这种信息需要密文存储,但是需要时必须能解密。
都讲到这里了我们在多讲点,相对于数据库而言,日志被泄漏的可能性更大,如果你的日志中大量打印了这些信息,也一样泄漏之后会被坏人利用,意味着除了数据库存储上需要做脱敏,日志打印也是要做的。日志打印中可以采用简单暴力的方法,直接屏蔽这些字段输出即可(可以用*号直接代替),不用考虑可逆性。
那么数据在传输的过程中是不是也会出现这种问题呢?答案是肯定的,当你的数据被中间人拦截的时候,一样会泄漏这些数据。所以生产环境我们一定要使用https协议。所以前端发起请求时一般也会要求传输的数据需要加密。不过相对而言此处的数据传输,上的要求没有对日志于数据库存储上来的严格,一般只要求密码做个加密即可。
脱敏后带来什么问题
数据脱敏之后,在数据库存储的就是个密文,那么假如现在有个需求,根据手机号模糊查询。你会发现对于这个密文完全行不通。
那么有没有解决办法呢?
解决方案一
先分词再组合,比如手机号 18850473300,这样一个手机号我们可以将它分段,然后按段进行加密后拼接起来存储,后续接收的模糊查询字段按一样的方式分段,加密然后查询比如分为344手机号可分为188、5047、3300,这个也是有科学依据的,一般人对数字的输入正常情况下都是3-4位数。
解决方案二
使用ShardingSphere 5.3 版本中的CharDigestLikeEncryptAlgorithm 算法进行like查询
具体实施
方案一
扩展mybatis的typeHandler,但是这种方式对业务层有轻微的入侵。个人认为并不是太好。
参考扩展 开源组件原理比较简单我就不介绍了。
方案二
使用ShardingSphere组件,这种方式对业务代码是0入侵
这两个地址的文档都可以看看
这个原理是通过代理数据源datasource拦截了sql处理中的prepareStatment阶段,和resultset阶段的处理,在预编译阶段从配置文件查询对应sql中存不存在需要配置的表和字段,如果存在就将对应的参数进行加密,在结果集中对对应的字段进行解密。
以下集成方案采用的是最简单粗暴的方式,当然你也可以使用官方提供的starter集成,只要代理数据源即可,剩下的仅需进行配置。集成起来是非常简单的。
引入依赖
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>4.0.1</version>
</dependency>
做个简单的配置抽取
@ConfigurationProperties(prefix = "user")
@Data
public class UserProperties {
private Datasource datasource;
private Encrypt encrypt;
@Data
public static class Datasource {
private String type;
private String driverClassName;
private String url;
private String username;
private String password;
}
@Data
public static class Encrypt {
//key=table , value=columns
private Map<String, List<String>> tableFields;
private String aesSecret;
}
}
@Configuration
@EnableConfigurationProperties(UserProperties.class)
@MapperScan(basePackages = "com.xxx.xxx.xxx.mapper.user",sqlSessionFactoryRef = "userSqlSessionFactory")
public class UserDataSourceConfiguration {
@Bean("userSqlSessionFactory")
SqlSessionFactory sqlSessionFactory(@Qualifier("userDataSource") DataSource dataSource,
ObjectProvider<Interceptor[]> plugins)
throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
sqlSessionFactoryBean.setVfs(SpringBootVFS.class);
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setPlugins(plugins.getIfAvailable());
sqlSessionFactoryBean.setTypeAliasesPackage("com.xxx.xxx.xxx.entity.user");
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
sqlSessionFactoryBean.setMapperLocations(resourceResolver.getResources("classpath:sql/user/**/*Mapper.xml"));
return sqlSessionFactoryBean.getObject();
}
@Bean("userDataSource")
public DataSource dataSource(UserProperties properties) throws SQLException {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName(properties.getDatasource().getDriverClassName());
dataSource.setJdbcUrl(properties.getDatasource().getUrl());
dataSource.setUsername(properties.getDatasource().getUsername());
dataSource.setPassword(properties.getDatasource().getPassword());
// 配置脱敏规则
Properties props = new Properties();
props.setProperty("aes.key.value", properties.getEncrypt().getAesSecret());
EncryptorRuleConfiguration encryptorConfig = new EncryptorRuleConfiguration("AES", props);
// 配置脱敏规则
EncryptRuleConfiguration config = new EncryptRuleConfiguration();
this.setEncryptRuleConfiguration(config, encryptorConfig, properties);
// 获取数据源对象
return EncryptDataSourceFactory.createDataSource(dataSource, config, new Properties());
}
public void setEncryptRuleConfiguration(EncryptRuleConfiguration encryptRuleConfig,
EncryptorRuleConfiguration encryptorConfig, UserProperties properties) {
properties.getEncrypt().getTableFields().forEach((tables, fieldList) -> {
Map<String, EncryptColumnRuleConfiguration> columns = Maps.newHashMap();
fieldList.forEach(field -> {
//字段配置
EncryptColumnRuleConfiguration columnConfig = new EncryptColumnRuleConfiguration("",
field, "", "aes");
columns.put(field, columnConfig);
//解密方式
encryptRuleConfig.getEncryptors().put("aes", encryptorConfig);
});
//字段对应字段配置
EncryptTableRuleConfiguration tableConfig = new EncryptTableRuleConfiguration(columns);
//表
encryptRuleConfig.getTables().put(tables, tableConfig);
});
}
}
配置文件
user:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/user?serverTimezone=GMT%2B8&autoReconnect=true&allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false
username: root
password: root
encrypt:
aes-secret: "hahahahhah" # aes密钥
tableFields: #加密字段配置
u_user: # 表
- user_account #表下的字段
- mobile
- id_no
- id_address
u_enterprise: #另一张表
- bank_card_no #另一张表的字段
- business_license_no
存量数据处理
当然不管那种方案下,如果你是从0开始那么直接配置即可,如果你已有存量数据,那么你需要对这些存量数据进行清洗,也就是存量数据你先得写个接口先进行加密,不然直接跑起来会出错,当然你也可以采用官方文档提供的方案,使用辅组字段。个人感觉没什么必要,这种东西又不会经常变来变去的。
转载:https://blog.csdn.net/a807719447/article/details/129078392