springmvc-数据脱敏

在项目开发过程中,通常会有一些敏感字段如:电话号码,身份证,email等信息,不想完整暴露给客户端,这里就需要用到数据脱敏.
常见的脱敏手段有:

  • 数据库编写脱敏函数
  • mybatis编写脱敏拦截器

今天来介绍一种新思路。
基于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;
	}
}

你可能感兴趣的:(#,springMVC,数据脱敏)