目录
- SpringBoot项目中新增脱敏功能
- 项目背景
- 项目需求描述
- 项目解决方案
- 1. 解决方案
- 2. 实现代码
- 2.1 注解 Sensitive
- 2.1 脱敏类型枚举 SensitiveType
- 2.3 脱敏工具 DesensitizedUtils
- 3 使用实例
- 3.1 需注解对象
- 3.2 脱敏操作
SpringBoot项目中新增脱敏功能
项目背景
目前正在开发一个SpringBoot项目,此项目有Web端和微信小程序端。web端提供给工作人员使用,微信小程序提供给群众进行预约操作。项目中有部分敏感数据需要脱敏传递给微信小程序,给与群众查看。
项目需求描述
项目中,由于使用端有两个,对于两个端的数据权限并不一样。Web端可以查看所有数据,小程序端只能查看脱敏后的数据。
需要开发一个通用脱敏功能
- 手动进行脱敏操作
- 支持多种对象,
- 支持不同字段,并脱敏指定字段
- 字段的脱敏方式多样
- 字段的脱敏方式可自定义
项目解决方案
1. 解决方案
使用注解方式,来支持对指定字段,不同字段,多种脱敏操作,并可以脱离对象。
使用工具对象,通过泛型传参,来支持对不同对象的脱敏操作。
2. 实现代码
2.1 注解 Sensitive
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义数据脱敏
*
* 例如: 身份证,手机号等信息进行模糊处理
*
* @author lzddddd
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sensitive {
/**
* 脱敏数据类型
*/
SensitiveType type() default SensitiveType.CUSTOMER;
/**
* 前置不需要打码的长度
*/
int prefixNoMaskLen() default;
/**
* 后置不需要打码的长度
*/
int suffixNoMaskLen() default;
/**
* 用什么打码
*/
String symbol() default "*";
}
2.1 脱敏类型枚举 SensitiveType
public enum SensitiveType {
/**
* 自定义
*/
CUSTOMER,
/**
* 名称
**/
CHINESE_NAME,
/**
* 身份证证件号
**/
ID_CARD_NUM,
/**
* 手机号
**/
MOBILE_PHONE,
/**
* 固定电话
*/
FIXED_PHONE,
/**
* 密码
**/
PASSWORD,
/**
* 银行卡号
*/
BANKCARD,
/**
* 邮箱
*/
EMAIL,
/**
* 地址
*/
ADDRESS,
}
2.3 脱敏工具 DesensitizedUtils
import com.ruoyi.common.annotation.Sensitive;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.SensitiveType;
import lombok.extern.slfj.Slf4j;
import java.lang.reflect.Field;
import java.util.*;
@Slfj
public class DesensitizedUtils<T> {
/**
* 脱敏数据列表
*/
private List<T> list;
/**
* 注解列表
*/
private List<Object[]> fields;
/**
* 实体对象
*/
public Class<T> clazz;
public DesensitizedUtils(Class<T> clazz)
{
this.clazz = clazz;
}
/**
* 初始化数据
*
* @param list 需要处理数据
*/
public void init(List<T> list){
if (list == null)
{
list = new ArrayList<T>();
}
this.list = list;
// 得到所有定义字段
createSensitiveField();
}
/**
* 初始化数据
*
* @param t 需要处理数据
*/
public void init(T t){
list = new ArrayList<T>();
if (t != null)
{
list.add(t);
}
// 得到所有定义字段
createSensitiveField();
}
/**
* 得到所有定义字段
*/
private void createSensitiveField()
{
this.fields = new ArrayList<Object[]>();
List<Field> tempFields = new ArrayList<>();
tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
for (Field field : tempFields)
{
// 单注解
if (field.isAnnotationPresent(Sensitive.class))
{
putToField(field, field.getAnnotation(Sensitive.class));
}
// 多注解
// if (field.isAnnotationPresent(Excels.class))
// {
// Excels attrs = field.getAnnotation(Excels.class);
// Excel[] excels = attrs.value();
// for (Excel excel : excels)
// {
// putToField(field, excel);
// }
// }
}
}
/**
* 对list数据源将其里面的数据进行脱敏处理
*
* @param list
* @return 结果
*/
public AjaxResult desensitizedList(List<T> list){
if (list == null){
return AjaxResult.error("脱敏数据为空");
}
// 初始化数据
this.init(list);
int failTimes =;
for (T t: this.list) {
if ((Integer)desensitization(t).get("code") != HttpStatus.SUCCESS){
failTimes++;
}
}
if (failTimes >){
return AjaxResult.error("脱敏操作中出现失败",failTimes);
}
return AjaxResult.success();
}
/**
* 放到字段集合中
*/
private void putToField(Field field, Sensitive attr)
{
if (attr != null)
{
this.fields.add(new Object[] { field, attr });
}
}
/**
* 脱敏:JavaBean模式脱敏
*
* @param t 需要脱敏的对象
* @return
*/
public AjaxResult desensitization(T t) {
if (t == null){
return AjaxResult.error("脱敏数据为空");
}
// 初始化数据
init(t);
try {
// 遍历处理需要进行 脱敏的字段
for (Object[] os : fields)
{
Field field = (Field) os[];
Sensitive sensitive = (Sensitive) os[];
// 设置实体类私有属性可访问
field.setAccessible(true);
desensitizeField(sensitive,t,field);
}
return AjaxResult.success(t);
} catch (Exception e) {
e.printStackTrace();
log.error("日志脱敏处理失败,回滚,详细信息:[{}]", e);
return AjaxResult.error("脱敏处理失败",e);
}
}
/**
* 对类的属性进行脱敏
*
* @param attr 脱敏参数
* @param vo 脱敏对象
* @param field 脱敏属性
* @return
*/
private void desensitizeField(Sensitive attr, T vo, Field field) throws IllegalAccessException {
if (attr == null || vo == null || field == null){
return ;
}
// 读取对象中的属性
Object value = field.get(vo);
SensitiveType sensitiveType = attr.type();
int prefixNoMaskLen = attr.prefixNoMaskLen();
int suffixNoMaskLen = attr.suffixNoMaskLen();
String symbol = attr.symbol();
//获取属性后现在默认处理的是String类型,其他类型数据可扩展
Object val = convertByType(sensitiveType, value, prefixNoMaskLen, suffixNoMaskLen, symbol);
field.set(vo, val);
}
/**
* 以类的属性的get方法方法形式获取值
*
* @param o 对象
* @param name 属性名
* @return value
* @throws Exception
*/
private Object getValue(Object o, String name) throws Exception
{
if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name))
{
Class<?> clazz = o.getClass();
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
o = field.get(o);
}
return o;
}
/**
* 根据不同注解类型处理不同字段
*/
private Object convertByType(SensitiveType sensitiveType, Object value, int prefixNoMaskLen, int suffixNoMaskLen, String symbol) {
switch (sensitiveType) {
case CUSTOMER:
value = customer(value, prefixNoMaskLen, suffixNoMaskLen, symbol);
break;
case CHINESE_NAME:
value = chineseName(value, symbol);
break;
case ID_CARD_NUM:
value = idCardNum(value, symbol);
break;
case MOBILE_PHONE:
value = mobilePhone(value, symbol);
break;
case FIXED_PHONE:
value = fixedPhone(value, symbol);
break;
case PASSWORD:
value = password(value, symbol);
break;
case BANKCARD:
value = bankCard(value, symbol);
break;
case EMAIL:
value = email(value, symbol);
break;
case ADDRESS:
value = address(value, symbol);
break;
}
return value;
}
/*--------------------------下面的脱敏工具类也可以单独对某一个字段进行使用-------------------------*/
/**
* 【自定义】 根据设置进行配置
*
* @param value 需处理数据
* @param symbol 填充字符
* @return 脱敏后数据
*/
public Object customer(Object value, int prefixNoMaskLen, int suffixNoMaskLen, String symbol) {
//针对字符串的处理
if (value instanceof String){
return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
}
return value;
}
/**
* 对字符串进行脱敏处理
*
* @param s 需处理数据
* @param prefixNoMaskLen 开头展示字符长度
* @param suffixNoMaskLen 结尾展示字符长度
* @param symbol 填充字符
* @return
*/
private String handleString(String s, int prefixNoMaskLen, int suffixNoMaskLen, String symbol){
// 是否为空
if (StringUtils.isBlank(s)) {
return "";
}
// 如果设置为空之类使用 * 代替
if (StringUtils.isBlank(symbol)){
symbol = "*";
}
// 对长度进行判断
int length = s.length();
if (length > prefixNoMaskLen + suffixNoMaskLen){
String namePrefix = StringUtils.left(s, prefixNoMaskLen);
String nameSuffix = StringUtils.right(s, suffixNoMaskLen);
s = StringUtils.rightPad(namePrefix, StringUtils.length(s) - suffixNoMaskLen, symbol).concat(nameSuffix);
}
return s;
}
/**
* 【中文姓名】只显示第一个汉字,其他隐藏为个星号,比如:李**
*
* @param value 需处理数据
* @param symbol 填充字符
* @return 脱敏后数据
*/
public String chineseName(Object value, String symbol) {
//针对字符串的处理
if (value instanceof String){
// 对前后长度进行设置 默认 开头只展示一个字符
int prefixNoMaskLen =;
int suffixNoMaskLen =;
return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
}
return "";
}
/**
* 【身份证号】显示最后四位,其他隐藏。共计位或者15位,比如:*************1234
*
* @param value 需处理数据
* @param symbol 填充字符
* @return 脱敏后数据
*/
public String idCardNum(Object value, String symbol) {
//针对字符串的处理
if (value instanceof String){
// 对前后长度进行设置 默认 结尾只展示四个字符
int prefixNoMaskLen =;
int suffixNoMaskLen =;
return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
}
return "";
}
/**
* 【固定电话】 显示后四位,其他隐藏,比如:*******
*
* @param value 需处理数据
* @param symbol 填充字符
* @return 脱敏后数据
*/
public String fixedPhone(Object value, String symbol) {
//针对字符串的处理
if (value instanceof String){
// 对前后长度进行设置 默认 结尾只展示四个字符
int prefixNoMaskLen =;
int suffixNoMaskLen =;
return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
}
return "";
}
/**
* 【手机号码】前三位,后四位,其他隐藏,比如:****6810
*
* @param value 需处理数据
* @param symbol 填充字符
* @return 脱敏后数据
*/
public String mobilePhone(Object value, String symbol) {
//针对字符串的处理
if (value instanceof String){
// 对前后长度进行设置 默认 开头只展示三个字符 结尾只展示四个字符
int prefixNoMaskLen =;
int suffixNoMaskLen =;
return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
}
return "";
}
/**
* 【地址】只显示到地区,不显示详细地址,比如:湖南省长沙市岳麓区***
* 只能处理 省市区的数据
*
* @param value 需处理数据
* @param symbol 填充字符
* @return
*/
public String address(Object value, String symbol) {
//针对字符串的处理
if (value instanceof String){
// 对前后长度进行设置 默认 开头只展示九个字符
int prefixNoMaskLen =;
int suffixNoMaskLen =;
return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
}
return "";
}
/**
* 【电子邮箱】 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@.com
*
* @param value 需处理数据
* @param symbol 填充字符
* @return 脱敏后数据
*/
public String email(Object value, String symbol) {
//针对字符串的处理
if (value instanceof String){
// 对前后长度进行设置 默认 开头只展示一个字符 结尾只展示@及后面的地址
int prefixNoMaskLen =;
int suffixNoMaskLen =;
String s = (String) value;
if (StringUtils.isBlank(s)) {
return "";
}
// 获取最后一个@
int lastIndex = StringUtils.lastIndexOf(s, "@");
if (lastIndex <=) {
return s;
} else {
suffixNoMaskLen = s.length() - lastIndex;
}
return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
}
return "";
}
/**
* 【银行卡号】前六位,后四位,其他用星号隐藏每位个星号,比如:6222600**********1234
*
* @param value 需处理数据
* @param symbol 填充字符
* @return 脱敏后数据
*/
public String bankCard(Object value, String symbol) {
//针对字符串的处理
if (value instanceof String){
// 对前后长度进行设置 默认 开头只展示六个字符 结尾只展示四个字符
int prefixNoMaskLen =;
int suffixNoMaskLen =;
return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
}
return "";
}
/**
* 【密码】密码的全部字符都用*代替,比如:******
*
* @param value 需处理数据
* @param symbol 填充字符
* @return
*/
public String password(Object value,String symbol) {
//针对字符串的处理
if (value instanceof String){
// 对前后长度进行设置 默认 开头只展示六个字符 结尾只展示四个字符
int prefixNoMaskLen =;
int suffixNoMaskLen =;
return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
}
return "";
}
}
3 使用实例
3.1 需注解对象
public class User {
private static final long serialVersionUID =L;
/** 普通用户ID */
private Long userId;
/** 昵称 */
@Sensitive(type = SensitiveType.CUSTOMER,prefixNoMaskLen =,suffixNoMaskLen = 1)
private String nickName;
/** 姓名 */
@Sensitive(type = SensitiveType.CHINESE_NAME)
private String userName;
/** 身份证 */
@Sensitive(type = SensitiveType.ID_CARD_NUM)
private String identityCard;
/** 手机号码 */
@Sensitive(type = SensitiveType.MOBILE_PHONE)
private String phoneNumber;
}
3.2 脱敏操作
// 脱敏对象
User user = new User();
......
DesensitizedUtils<User> desensitizedUtils = new DesensitizedUtils<>(User.class);
desensitizedUtils.desensitization(user);
//脱敏队列
List<User> users = new ArrayList<>();
......
DesensitizedUtils<User> desensitizedUtils = new DesensitizedUtils<>(User.class);
desensitizedUtils.desensitizedList(users);