在项目开发过程中,通常会有一些敏感字段如:电话号码,身份证,email等信息,不想完整暴露给客户端,这里就需要用到数据脱敏
.
常见的脱敏手段有:
脱敏函数
脱敏拦截器
今天来介绍一种新思路。
基于ResponseBodyAdvice
来实现,关于ResponseBodyAdvice
在之前的文章SpringMVC-特性annotation中有介绍,这里不再赘述.
public interface ResponseBodyAdvice<T> {
//判断是否支持修改内容
boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);
// 修改内容
T beforeBodyWrite(T body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response);
}
ResponseBodyAdvice
仅拦截Controller
中所有带有@ResponseBody
的方法.
1.标记方法是否需要进行脱敏
首先要对所有需要脱敏的方法或类
添加标记@DesensitizeSupport
,用来表明方法是否需要进行脱敏(若注解在类上,则说明该类的所有的方法都需要脱敏)
/**
* 标记:方法,类 需要使用数据脱敏
*/
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { ElementType.TYPE,ElementType.METHOD })
@Documented
public @interface DesensitizeSupport {
}
例
:
@DesensitizeSupport
@Controller
@RequestMapping("/demo")
public class DemoController {
@GetMapping("/list")
public List<Demo> qryDemo(Demo param) {
//todo.....
}
}
2. 字段脱敏策略
首先定义常用的脱敏方式(enum):
public enum DesensitizeType {
NAME, // 名称
ID_CARD_15, //身份证 15
ID_CARD_18, //身份证 18
EMAIL,
MOBILE_PHONE; //手机号
}
然后在各个返回页面的数据对象bean中,需要脱敏的字段添加@Desensitize
注解,并指定脱敏方式。
/**
* 标记:字段使用何种策略来脱敏
*/
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = {ElementType.FIELD})
@Documented
public @interface Desensitize {
DesensitizeType type();
}
例
:
@Data
public class Demo {
private Long id;
private String name;
@Desensitize(type = DesensitizeType.MOBILE_PHONE)
private String mobilePhone;
@Desensitize(type = DesensitizeType.ID_CARD_18)
private String idCard;
}
3. ResponseBodyAdviced脱敏
使用ResponseBodyAdviced
拦截即将返回对请求,进行脱敏操作:
@ControllerAdvice
public class DesensitizeResponseBodyAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
Method method = returnType.getMethod();
//a. 首先判断该方法是否存在@DesensitizeSupport注解
//b. 如果不存在,再判断该方法所在类是否存在注解
boolean exist = method.getAnnotation(DesensitizeSupport.class) != null || method.getDeclaringClass().getAnnotation(DesensitizeSupport.class) != null;
return exist;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if(body == null){
return null;
}
Collection collection;
//1.判断返回值的类型,构造成一个集合对象
if (body instanceof Collection) {
collection = (Collection) body;
} else if(body.getClass().isArray()){
if (body.getClass().getComponentType().isPrimitive()) {
return body;
}
collection = Arrays.asList(body);
}else {
collection = Collections.singletonList(body);
}
// 进行脱敏
collection.stream().forEach(obj -> {
Class clazz = obj.getClass();
Arrays.stream(clazz.getDeclaredFields()).filter(field1 ->
//2. 获取class中 所有带有@Desensitize注解,且字段申明字段类型为String得 fields ,
field1.getAnnotation(Desensitize.class) != null && String.class == field1.getType()
).forEach(field2 ->{
//3. 针对满足条件2的字段进行脱敏
//3.1 获取field的脱敏类型
DesensitizeType type = field2.getAnnotation(Desensitize.class).type();
//3.2 获取field的值
String oldValue = field2.get(obj);
//3.3 获取脱敏类型type,并针对不同对脱敏类型进行脱敏,获取newValue
String newValue = replace(oldValue, type);
//3.4 反射设置newValue
field2.setAccessible(true);
field2.set(obj,newValue);
});
});
return body;
}
}