背景

现代网络环境中,敏感数据的处理是至关重要的。敏感数据包括个人身份信息、银行账号、手机号码等,泄露这些数据可能导致用户隐私泄露、财产损失等严重后果。因此,对敏感数据进行脱敏处理是一种必要的安全措施。

比如页面上常见的敏感数据都是加*遮挡处理过的,如下图所示。

接下来本文将以Spring Boot和MyBatis框架实现返回数据的脱敏处理。

脱敏工具

脱敏工具有很多种,本文主要介绍和使用hutool工具包提供的脱敏工具类DesensitizedUtil,它提供了常见的手机号、身份证号、银行卡、邮箱等脱敏的方法,将敏感数据部分加*处理。

使用方法如下:

maven项目需要导入hutool包依赖,坐标如下:

<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.4</version>
</dependency>
import cn.hutool.core.util.DesensitizedUtil;

public class SensitiveHutoolTest {
public static void main(String[] args) {
System.out.println(DesensitizedUtil.mobilePhone("13812345678"));
System.out.println(DesensitizedUtil.idCardNum("110101200007283706", 3, 4));
System.out.println(DesensitizedUtil.bankCard("6225809637392380845"));
System.out.println(DesensitizedUtil.email("zhangsanfeng@test.com"));
}
}

输出如下:

138****5678
110***********3706
6225 **** **** *** 0845
z***********@test.com

实现思路

  • 设计一个常用的脱敏类型枚举类。
  • 编写脱敏注解,包含脱敏类型,采用注解的方式对需要脱敏的字段进行标注。
  • 再编写脱敏注解相应的实现逻辑使用MyBatis的拦截器功能对查出的结果集采用反射的处理方式对结果进行脱敏处理,然后将脱敏处理后的结果集再返回。

代码实现

  1. 定义脱敏类型枚举

利用hutool工具包,对常见的手机号、身份证号、银行卡号、邮箱进行脱敏处理。还给了一个默认的DEFAULT枚举类型按原数据返回,同时自定义了一个CUSTOM枚举,可根据实现CustomMaskService这个接口去自定义脱敏逻辑。

注意:这里的CUSTOM只是举一个简单的例子,实际情况可能需要根据实际情况扩展输入输出参数等。

import cn.hutool.core.util.DesensitizedUtil;
import lombok.Getter; public enum SensitiveTypeEnum {
MOBILE("mobile", "手机号") {
@Override
public String maskSensitiveData(String data) {
//手机号前3位后4位脱敏,中间部分加*处理,比如:138****5678
return DesensitizedUtil.mobilePhone(data);
}
},
IDENTIFY("identify", "身份证号") {
@Override
public String maskSensitiveData(String data) {
//身份证前3位后4位脱敏,中间部分加*处理,比如:110***********3706
return DesensitizedUtil.idCardNum(data, 3, 4);
}
},
BANKCARD("bankcard", "银行卡号") {
@Override
public String maskSensitiveData(String data) {
//银行卡号前4位后4位脱敏,中间部分加*处理,比如:6225 **** **** *** 0845
return DesensitizedUtil.bankCard(data);
}
}, EMAIL("email", "邮箱") {
@Override
public String maskSensitiveData(String data) {
//邮箱@符号后明文显示,@符号前的字符串,只显示第一个字符,其余加*处理,比如:z***********@test.com
return DesensitizedUtil.email(data);
}
},
DEFAULT("default", "默认") {
@Override
public String maskSensitiveData(String data) {
// 默认原值返回,其他这个也没啥意义^_^
return data;
}
},
CUSTOM("custom", "自定义") {
@Override
public String maskSensitiveData(String data, CustomMaskService customMaskService) {
// 可以自定义处理的service,根据实际使用情况可能需要添加参数,调整一下即可
return customMaskService.maskData(data);
}
}; @Getter
private String type; @Getter
private String desc; SensitiveTypeEnum(String type, String desc) {
this.type = type;
this.desc = desc;
} /**
* 遮挡敏感数据
*
* @param data
* @return
*/
public String maskSensitiveData(String data) {
return data;
} public String maskSensitiveData(String data, CustomMaskService customMaskService) {
return null;
}
}
  1. 定义一个脱敏注解
import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SensitiveData {
SensitiveTypeEnum type() default SensitiveTypeEnum.DEFAULT;
}
  1. 定义并配置一个mybatis拦截器。
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.springframework.beans.factory.annotation.Autowired; import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.List;
import java.util.Map; @Intercepts({
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
@Slf4j
public class SensitiveDataInterceptor implements Interceptor { @Autowired
private CustomMaskService customMaskService; @Override
public Object intercept(Invocation invocation) throws Throwable {
Object result = invocation.proceed();
log.debug("进入数据脱敏拦截器...");
if (result instanceof List) {
List<?> resultList = (List<?>) result;
for (Object obj : resultList) {
doSensitiveFields(obj);
}
} else if (result instanceof Map) {
Map<?, ?> resultMap = (Map<?, ?>) result;
for (Object obj : resultMap.values()) {
doSensitiveFields(obj);
}
} else {
doSensitiveFields(result);
}
return result;
} private void doSensitiveFields(Object obj) throws IllegalAccessException {
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(SensitiveData.class)) {
field.setAccessible(true);
Object value = field.get(obj);
if (value == null) {
return;
}
SensitiveData sensitiveData = field.getAnnotation(SensitiveData.class);
SensitiveTypeEnum type = sensitiveData.type();
String result;
if (type == SensitiveTypeEnum.CUSTOM) {
result = type.maskSensitiveData(value.toString(), customMaskService);
} else {
result = type.maskSensitiveData(value.toString());
}
field.set(obj, result);
}
}
}
}

拦截器注解里写明了要拦截的对象和方法,类:ResultSetHandler,方法:handleResultSets,它是在sql执行完成后拿到结果集并对结果集进行处理再返回。

  1. 配置mybatis及拦截器
import com.star95.project.study.mybatisplus.interceptor.SensitiveDataInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
@MapperScan("com.star95.project.study.mybatisplus.mapper")
public class MyBatisPlusConfig { @Bean
public SensitiveDataInterceptor sensitiveDataInterceptor() {
return new SensitiveDataInterceptor();
}
}
  1. 测试
import com.star95.project.study.mybatisplus.interceptor.SensitiveData;
import lombok.Data; import static com.star95.project.study.mybatisplus.interceptor.SensitiveTypeEnum.*; @Data
public class UserDto {
/**
* id
*/
private Long id;
/**
* 姓名
*/
@SensitiveData(type = CUSTOM)
private String name;
/**
* 年龄
*/
private Integer age; /**
* 邮箱
*/
@SensitiveData(type = EMAIL)
private String email; /**
* 手机号
*/
@SensitiveData(type = MOBILE)
private String mobile; /**
* 身份证号
*/
@SensitiveData(type = IDENTIFY)
private String identify; /**
* 银行卡号
*/
@SensitiveData(type = BANKCARD)
private String bankcard;
}
public interface CustomMaskService {
String maskData(String data);
}
import cn.hutool.core.text.CharSequenceUtil;
import org.springframework.stereotype.Service; @Service
public class CustomMaskServiceImpl implements CustomMaskService {
@Override
public String maskData(String data) {
//第一个字符明文外,其他都加*处理
return CharSequenceUtil.hide(data, 1, data.length());
}
}
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.star95.project.study.mybatisplus.dto.User;
import com.star95.project.study.mybatisplus.dto.UserDto;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select; import java.util.List; @Mapper
public interface UserMapper extends BaseMapper<User> {
@Select({"select * from user where id=#{id}"})
UserDto getSpecialUser(String id); @Select({"select * from user"})
List<UserDto> queryAll();
}
import com.star95.project.study.mybatisplus.dto.UserDto;
import com.star95.project.study.mybatisplus.mapper.UserMapper;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource;
import java.util.List; @RestController
@RequestMapping("/sensitive")
public class SensitiveMybatisInterceptorTestController {
@Resource
private UserMapper userMapper; @GetMapping("/user/{id}")
public Result<UserDto> queryUserInfo(@PathVariable String id) {
return Result.success(userMapper.getSpecialUser(id));
} @GetMapping("/userlist")
public Result<List<UserDto>> queryUserList() {
return Result.success(userMapper.queryAll());
}
}

写了两个测试接口,一个是查询单个对象,一个是返回list集合列表,访问一下:

可以看到单个结果和集合列表都做了脱敏处理,这样功能就实现完成了。

其他

使用mybatis拦截器这种方式,是在数据持久层的逻辑处理,需要注意的是,查询结果返回的dto如果是共享的情况下,可能会把不需要脱敏的数据也给处理了,影响业务逻辑,所以使用过程中要注意区分。

另外也可在接口返回数据时进行脱敏处理,也就是所说的序列化方式,比如自定义Jackson、fastjson等的序列化逻辑同样可以完成数据脱敏。

总结

通过使用MyBatis拦截器,我们可以实现对敏感数据的优雅脱敏处理,保护用户隐私和数据安全。这种方式可以灵活应用于各种场景,提供了一种简单而强大的解决方案。在实际开发中,我们可以根据具体需求,定制化开发拦截器的逻辑,以满足不同的数据脱敏需求。

数据的隐私和安全是非常重要的,敏感数据除存储方面要加密外,再展示方便也要适当的做脱敏处理,本文只介绍了mybatis拦截器实现的一种数据脱敏方式,还有很多其他技术可以实现,可以自行搜索,根据实际情况选择合适的解决方案。

MyBatis拦截器优雅实现数据脱敏的更多相关文章

  1. 解决mybatis拦截器无法注入spring bean的问题

    公司要整合rabbitmq与mybatis拦截器做一个数据同步功能. 整合过程中大部分环节都没什么问题,就是遇到了mybatis拦截器 @Intercepts(@Signature(type = Ex ...

  2. 数据权限管理中心 - 基于mybatis拦截器实现

    数据权限管理中心 由于公司大部分项目都是使用mybatis,也是使用mybatis的拦截器进行分页处理,所以技术上也直接选择从拦截器入手 需求场景 第一种场景:行级数据处理 原sql: select ...

  3. 通过自定义拦截器优雅的导出Excel并标红的重复数据

    平时我们导入导出Excel的时候如果用poi导出,会发现光设置格式都要很多代码,看起来非常的不优雅.后来业务中遇到了需要导入非常巨大的Excel的需求.如果继续用poi的方式,因为poi把所有exce ...

  4. Mybatis拦截器,修改Date类型数据。设置毫秒为0

    1:背景 Mysql自动将datetime类型的毫秒数四舍五入,比如代码中传入的Date类型的数据值为  2021.03.31 23:59:59.700     到数据库   2021.04.01 0 ...

  5. Mybatis拦截器 mysql load data local 内存流处理

    Mybatis 拦截器不做解释了,用过的基本都知道,这里用load data local主要是应对大批量数据的处理,提高性能,也支持事务回滚,且不影响其他的DML操作,当然这个操作不要涉及到当前所lo ...

  6. Mybatis拦截器实现分页

    本文介绍使用Mybatis拦截器,实现分页:并且在dao层,直接返回自定义的分页对象. 最终dao层结果: public interface ModelMapper { Page<Model&g ...

  7. 基于Spring和Mybatis拦截器实现数据库操作读写分离

    首先需要配置好数据库的主从同步: 上一篇文章中有写到:https://www.cnblogs.com/xuyiqing/p/10647133.html 为什么要进行读写分离呢? 通常的Web应用大多数 ...

  8. "犯罪心理"解读Mybatis拦截器

    原文链接:"犯罪心理"解读Mybatis拦截器 Mybatis拦截器执行过程解析 文章写过之后,我觉得 "Mybatis 拦截器案件"背后一定还隐藏着某种设计动 ...

  9. 玩转SpringBoot之整合Mybatis拦截器对数据库水平分表

    利用Mybatis拦截器对数据库水平分表 需求描述 当数据量比较多时,放在一个表中的时候会影响查询效率:或者数据的时效性只是当月有效的时候:这时我们就会涉及到数据库的分表操作了.当然,你也可以使用比较 ...

  10. Mybatis拦截器实现原理深度分析

    1.拦截器简介 拦截器可以说使我们平时开发经常用到的技术了,Spring AOP.Mybatis自定义插件原理都是基于拦截器实现的,而拦截器又是以动态代理为基础实现的,每个框架对拦截器的实现不完全相同 ...

随机推荐

  1. C补充

    C语言之unsigned 与signed 无符号与有符号数据的操作区别在于当最高位,当最高位为0时都一样: 在C中,默认的基础数据类型均为signed,现在我们以char为例,说明(signed) c ...

  2. 用git bash 生成 hexo 博客文件,报错node: command not found

    有git bash,也安装了node.js, 为什么在bash里面会说node: command not found? 电脑有段时间不能上网,下载了360系统急救箱,删了很多文件,包括一些exe文件, ...

  3. 【EF Core】主从实体关系与常见实体关系的区别

    上次老周扯了有关主.从实体的话题,本篇咱们再挖一下,主.从实体之间建立的关系,跟咱们常用的一对一.一对多这些关系之间有什么不同. 先看看咱们从学习数据库开始就特熟悉的常用关系--多对多.一对一.一对多 ...

  4. pthon之字典的遍历

    对字典的操作稍有些陌生,在此记录一下. 字典的使用已{key:value}的形式存在,多个值以逗号分开. 字典的遍历共有三种方法,他们将返回类似列表的值,分别对应字典的键.值.键-值对.即keys() ...

  5. 犯得一些zz错误

    本文用于警戒自己,不要再犯以前的傻逼错误 noip没建子文件夹导致爆零 知道关同步流之后还用endl,导致超时 使用'\n'代替endl 3.多组测试数据使用for循环占用了 i 变量名,后面在for ...

  6. 论文解读(MetaAdapt)《MetaAdapt: Domain Adaptive Few-Shot Misinformation Detection via Meta Learning》

    Note:[ wechat:Y466551 | 可加勿骚扰,付费咨询 ] 论文信息 论文标题:MetaAdapt: Domain Adaptive Few-Shot Misinformation De ...

  7. 《SQL与数据库基础》18. MySQL管理

    目录 MySQL管理 系统数据库 常用工具 mysql mysqladmin mysqlbinlog mysqlshow mysqldump mysqlimport source 本文以 MySQL ...

  8. ECharts图表动态修改series显示隐藏

    目录 1.前言 2.思路 3.实现 1.前言 最近做的大数据平台,里面很多地方用到了ECharts,其中有个功能,要求将图表分组,根据用户选择的组,来确定ECharts要显示那些线条和柱子,也就是动态 ...

  9. CodeForces 1367F1 Flying Sort (Easy Version)

    题意 给一个长度为\(n\)的数组,数组中的数互不相同,你可以有两种操作 将某一个数放置在数组开头 将某一个数放置在数组结尾 问最小操作多少次可以得到一个递增数列 分析 因为数组中的数很大,我们可以将 ...

  10. 微信小程序 setData accepts an Object rather than some undefined 解决办法

    问题 setData accepts an Object rather than some undefined setData接受一个对象而不是一些定义 让我猜猜, 你一定是在加载index页面(首页 ...