现代网络环境中,敏感数据的处理是至关重要的。敏感数据包括个人身份信息、银行账号、手机号码等,泄露这些数据可能导致用户隐私泄露、财产损失等严重后果。因此,对敏感数据进行脱敏处理是一种必要的安全措施。
比如页面上常见的敏感数据都是加*遮挡处理过的,如下图所示。
接下来本文将以Spring Boot和MyBatis框架实现返回数据的脱敏处理。
脱敏工具有很多种,本文主要介绍和使用hutool工具包提供的脱敏工具类DesensitizedUtil
,它提供了常见的手机号、身份证号、银行卡、邮箱等脱敏的方法,将敏感数据部分加*处理。
使用方法如下:
maven项目需要导入hutool包依赖,坐标如下:
|
|
|
|
|
|
|
|
|
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("[email protected]")); |
|
} |
|
} |
输出如下:
138****5678 |
|
110***********3706 |
|
6225 **** **** *** 0845 |
|
z***********@test.com |
利用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; |
|
} |
|
} |
定义一个脱敏注解
import java.lang.annotation.*; |
|
@Documented |
|
@Retention(RetentionPolicy.RUNTIME) |
|
@Target(ElementType.FIELD) |
|
public @interface SensitiveData { |
|
SensitiveTypeEnum type() default SensitiveTypeEnum.DEFAULT; |
|
} |
定义并配置一个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执行完成后拿到结果集并对结果集进行处理再返回。
配置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(); |
|
} |
|
} |
|
测试
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 |
|
@Select({"select * from user where id=#{id}"}) |
|
UserDto getSpecialUser(String id); |
|
@Select({"select * from user"}) |
|
List |
|
} |
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 |
|
return Result.success(userMapper.getSpecialUser(id)); |
|
} |
|
@GetMapping("/userlist") |
|
public Result |
|
return Result.success(userMapper.queryAll()); |
|
} |
|
} |
写了两个测试接口,一个是查询单个对象,一个是返回list集合列表,访问一下:
可以看到单个结果和集合列表都做了脱敏处理,这样功能就实现完成了。
使用mybatis拦截器这种方式,是在数据持久层的逻辑处理,需要注意的是,查询结果返回的dto如果是共享的情况下,可能会把不需要脱敏的数据也给处理了,影响业务逻辑,所以使用过程中要注意区分。
另外也可在接口返回数据时进行脱敏处理,也就是所说的序列化方式,比如自定义Jackson、fastjson等的序列化逻辑同样可以完成数据脱敏。
通过使用MyBatis拦截器,我们可以实现对敏感数据的优雅脱敏处理,保护用户隐私和数据安全。这种方式可以灵活应用于各种场景,提供了一种简单而强大的解决方案。在实际开发中,我们可以根据具体需求,定制化开发拦截器的逻辑,以满足不同的数据脱敏需求。
数据的隐私和安全是非常重要的,敏感数据除存储方面要加密外,再展示方便也要适当的做脱敏处理,本文只介绍了mybatis拦截器实现的一种数据脱敏方式,还有很多其他技术可以实现,可以自行搜索,根据实际情况选择合适的解决方案。