登录/注册
小开开
2649
占位
2
占位
0
浏览量
占位
粉丝
占位
关注
数据脱敏:姓名、电话号码等进行字段脱敏,中间部分显示成**
小开开
2021-09-24 21:48:13 2021-09-24
245
0

在前端展示时,有时需要将名字、电话号码、身份证等敏感信息过滤展示(脱敏),这种一般需要后端处理,提前将敏感信息过滤换成**的字样。

第一种方式是在每个页面展示时,去过滤,但是需要改动的地方非常多。实用性不强;

第二种方式是采用面向切面编程AOP相类似的方式,只需要写一个方法,然后在方法上加一个自定义类注解,在过滤的属性上加上类型注解就解决。

这里主要讲第二种方式 实现步骤为:

①定义脱敏的类型

②自定义类或方法的注解和字段的注解

③定义返回数据的格式

④实现脱敏的规则

⑤在字段和类上使用注解,声明脱敏的字段

⑥测试调用接口 获得结果

本博客涉及到的知识:

①如何自定义注解,以及各注解代表的含义

②了解特定注解@ControllerAdvice的含义以及接口ResponseBodyAdvice的机制

③反射获取数据,解析数据

④实现脱敏的逻辑

1.自定义注解

声明一个枚举脱敏类型

/**
* 数据脱敏类型
*/
public enum DesensitizeType {
NAME, // 名称
ID_CARD_18, //身份证 18
EMAIL,//email
MOBILE_PHONE; //手机号
}

 

   声明脱敏的字段 的注解(用在字段上)

/**
* 标记字段 使用何种策略来脱敏
*/
@Documented
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.FIELD})
@Inherited
public @interface Desensitize {
DesensitizeType type();
}

 

   声明脱敏的方法或类的注解

/**
* 标记在类、方法上,是否需要脱敏
*/
@Documented
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value={ElementType.METHOD, ElementType.TYPE})
@Inherited //说明子类可以继承父类中的该注解
public @interface DesensitizeSupport {
}

 

2.实现数据脱敏

定义响应的对象格式

/**
* 响应实体
*/
public class ResResult {
/**
* 编码
*/
private String code;
/**
* 提示信息
*/
private String message;
/**
* 数据
*/
private Object data;
//get //set...
}

 

数据的model,对要脱敏的字段加注解

@Desensitize

和脱敏类型DesensitizeType

public class UserModel implements Serializable {
/**
* 姓名
*/
@Desensitize(type = DesensitizeType.NAME)
private String name;
private Integer age;
private String desc;
/**
* 电话号码
*/
@Desensitize(type = DesensitizeType.MOBILE_PHONE)
private String telNumber;
//get //set...
}

 

controller层,在类或者方法上加注解

@DesensitizeSupport

表示该类或方法支持脱敏

@RestController
@RequestMapping("/test")
@DesensitizeSupport
public class UserController {
@Autowired
private IUserService iUserService;
@GetMapping(value = "/listuser")
public ResResult testHello() {
ResResult result = new ResResult();
List<UserModel> list = iUserService.listUser();
result.setData(list);
return result;
}
}

 

Service层

@Service
public class UserServiceImpl implements IUserService {
@Override
public List<UserModel> listUser() {
UserModel user = new UserModel();
user.setName("李四");
user.setAge(123);
ArrayList<UserModel> list = new ArrayList<>();
list.add(user);
return list;
}
}

 

   有了以上的部分后,还不会进行脱敏,还需要加上脱敏的具体操作。在Controller中执行了return语句后,在返回到前端之前,会执行如下代码进行脱敏:

/**
* 脱敏工具类
*/
public class DesensitizeUtils {
public static void main(String[] args) {
String name = "李明";
System.out.println(repVal(name, 1, 1));
}
public static String dataMasking(DesensitizeType type, String oldValue) {
String newVal = null;
switch (type) {
case NAME:
newVal = repVal(oldValue, 1, 1);
break;
case ID_CARD_18:
break;
case EMAIL:
break;
case MOBILE_PHONE:
break;
}
return newVal;
}
/**
* 字符替换
* @param val
* @param beg
* @param end
* @return
*/
public static String repVal(String val, int beg, int end) {
if (StringUtils.isEmpty(val)) {
return null;
}
String name = val.substring(0, beg);
int length = val.length();
if (length > 2 && length > end) {
return name + "**" + val.substring(length-end);
} else if (length == 2) {
return name + "*";
}
return val;
}
}

 

 

/**
* 统一处理 返回值/响应体
*/
@ControllerAdvice
public class DesensitizeResponseBodyAdvice implements ResponseBodyAdvice<Object> {
private final static Logger logger = LoggerFactory.getLogger(DesensitizeResponseBodyAdvice.class);
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
AnnotatedElement annotatedElement = returnType.getAnnotatedElement();
//1.首先判断该方法是否存在@DesensitizeSupport注解
//2.判断类上是否存在
Method method = returnType.getMethod();
DesensitizeSupport annotation = method.getAnnotation(DesensitizeSupport.class);
DesensitizeSupport clazzSup = method.getDeclaringClass().getAnnotation(DesensitizeSupport.class);
return annotation != null || clazzSup != null;
}
/**
*
* @param body
* @param returnType
* @param selectedContentType
* @param selectedConverterType
* @param request
* @param response
* @return
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
logger.debug("Test ResponseBodyAdvice ==> beforeBodyWrite:" + body.toString() + ";" + returnType);
Class<?> childClazz = body.getClass();
Field childField = null;
List filedValue = null;
try {
//获取数据
childField = childClazz.getDeclaredField("data");
//设置可访问
childField.setAccessible(true);
Object objs = childField.get(body);
if (!(objs instanceof List)) {
logger.debug("这不是List类型");
return body;
}
filedValue = (List) objs;
//对值进行脱敏
for (Object obj : filedValue) {
dealValue(obj);
}
} catch (NoSuchFieldException e) {
logger.error("未找到数据; message:" + e.getMessage());
e.printStackTrace();
} catch (IllegalAccessException e) {
logger.error("处理异常; message:" + e.getMessage());
e.printStackTrace();
}
return body;
}
public void dealValue(Object obj) throws IllegalAccessException {
Class<?> clazz = obj.getClass();
//获取奔雷和父类的属性
List<Field> fieldList = getAllFields(clazz);
for (Field field : fieldList) {
//获取属性上的注解
Desensitize annotation = field.getAnnotation(Desensitize.class);
if (annotation == null) {
continue;
}
Class<?> type = field.getType();
//判断属性的类型
if (String.class != type) {
//非字符串的类型 直接返回
continue;
}
//获取脱敏类型 判断是否脱敏
DesensitizeType annotType = annotation.type();
field.setAccessible(true);
String oldValue = (String) field.get(obj);
String newVal = DesensitizeUtils.dataMasking(annotType, oldValue);
field.set(obj, newVal);
}
}
/**
* 获取所有的字段(包括父类的)
* @param clazz
* @return
*/
public List<Field> getAllFields(Class<?> clazz) {
List<Field> fieldList = new ArrayList<>();
while (clazz != null) {
Field[] declaredFields = clazz.getDeclaredFields();
fieldList.addAll(Arrays.asList(declaredFields));
//获取父类,然后获取父类的属性
clazz = clazz.getSuperclass();
}
return fieldList;
}
}

 

3.结果

响应的结果,我们期待的两个字的名称【李四】会【李*】,三个字或三个以上的【李小明】会变成【李**明】(规则可自己进行设置)

 

注:在Controller层执行了return语句后,在返回到前端之前 会执行DesensitizeResponseBodyAdvice类中的supports和beforeBodyWrite方法,其中在类上有一个很重要的注解@ControllerAdvice

和很重要的接口

ResponseBodyAdvice

,这两个结合在一起,就具有统一处理返回值/响应体的功能。(相当于一个拦截器)

①@ControllerAdvice注解,这是一个Controller的增强型注解,可以实现三方面的功能:

  1. 全局异常处理
  2. 全局数据绑定
  3. 全局数据预处理

接口

ResponseBodyAdvice

继承了该接口,需要实现两个方法,supports和beforeBodyWrite方法。在supports方法返回为true后,才会执行beforeBodyWrite方法。其中beforeBodyWrite方法中的body就是响应对象response中的响应体,可以对响应体做统一的处理,比如加密、签名、脱敏等操作。

 

这里简单讲解一下其中的

注解

使用【@

interface

】是自定义一个注解,通常自定义的注解上面还有其他注解,如以下几个:

@Documented 表示标记这个注解是否会包含在文档中 @Retention 标识这个注解怎么保存,有三种状态,value = RetentionPolicy.RUNTIME 表示不仅保留在源码中,也保留在class中,并且在运行时可以访问;        SOURCE 表示只保留在源码中,当在class文件中时被遗弃;CLASS 表示保留在class文件中,但jvm加载class文件时被遗弃。 @Target 标注这个注解属于Java哪个成员,通常有属类、方法;字段;参数;包等 @Inherited 标记这个注解是继承于哪个注解类

 

 

 

 

 

 

 

 

 若需要完整的代码,请点【推荐】,然后留言。或觉得博文不错也请推荐留言,感谢你的支持。

 

原文: https://www.cnblogs.com/sun-flower1314/p/15117630.html

暂无评论