1

数据脱敏:姓名、电话号码等进行字段脱敏,中间部分显示成**

 2 years ago
source link: https://www.cnblogs.com/sun-flower1314/p/15117630.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

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

  第二种方式是通过面向切面编程AOP的方式,只需要写一个方法,然后在方法上加一个自定义注解就解决。

这里主要讲第二种方式

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;
    }
}

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

注:在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 标记这个注解是继承于哪个注解类

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK