【如何优雅的脱敏】:基于注解的方式

243 阅读4分钟

最近公司产品有个需求,需要对后台系统页面敏感字段进行脱敏。涉及的到页面很多,于是定义了一个基于注解的方式的通用脱敏工具类。

1、脱敏字段注解

/**
 * 自定义脱敏注解,用在需要脱敏的字段上
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sensitive {
    DesensitizationRule value();
    String replacement() default "";
}

在上述代码中,我们在 @Sensitive 注解中添加了一个 replacement 属性,用于指定脱敏替换字符串。在 desensitize 方法中,如果字段上的 @Sensitive 注解中指定了 replacement 属性,则使用该属性的值作为脱敏替换字符串;否则,使用脱敏规则中定义的默认替换字符串。

2、脱敏规则枚举

// 脱敏规则枚举
public enum DesensitizationRule {
    PHONE("(\d{3})\d{4}(\d{4})", "$1****$2"),
    ID_NUMBER("(\d{6})\d{8}(\w{4})", "$1********$2"),
    NAME("(?<=.).", "*");

    private final String regex;
    private final String replacement;

    DesensitizationRule(String regex, String replacement) {
        this.regex = regex;
        this.replacement = replacement;
    }

    public String getRegex() {
        return regex;
    }

    public String getReplacement() {
        return replacement;
    }
}

上述代码中的正则表达式 (\d{6})\d{8}(\w{4}) 表示匹配身份证号码前6位数字、8位数字和最后4位字符的部分。 $1$2 分别表示正则表达式中的第一个和第二个括号捕获的内容,用于保留前6位和最后4位的值,中间的8位数字用 ******** 替代。 使用这个正则表达式可以实现对身份证号码进行脱敏处理,保护敏感信息的隐私和安全。请根据具体的业务需求和隐私保护要求进行调整和扩展。

3、脱敏工具类

/**
 *  脱敏工具类 ,可对对象、数组、集合 进行脱敏
 */
public class DesensitizationUtils {
   
// 对象脱敏方法
  public static <T> T desensitize(T data) {
    Field[] fields = data.getClass().getDeclaredFields();
    for (Field field : fields) {
        if (field.isAnnotationPresent(Sensitive.class)) {
            field.setAccessible(true);
            try {
                Sensitive sensitive = field.getAnnotation(Sensitive.class);
                DesensitizationRule rule = sensitive.value();
                String replacement = sensitive.replacement().isEmpty() ? rule.getReplacement() : sensitive.replacement();
                String value = (String) field.get(data);
                if(null != value){
                    String desensitizedValue = value.replaceAll(rule.getRegex(), replacement);
                    field.set(data, desensitizedValue);
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
    return data;
}

//数组脱敏
public static <T> T[] desensitize(T[] arr) {
    for (int i = 0; i <arr.length ; i++) {
        T data= arr[i];
        desensitize(data);
    }
    return arr;
}

//集合脱敏
public static <T> Collection<T> desensitize(Collection<T> coll) {
    Iterator<T> iterator = coll.iterator();
    while (iterator.hasNext()) {
        T data = iterator.next();
         desensitize(data);
    }
  return coll;
}
}

通用的脱敏方法,它通过遍历数据对象的字段,对带有@Sensitive注解的字段进行脱敏处理。脱敏规则和替换字符串可以通过注解参数进行指定,如果没有指定,则使用默认的脱敏规则和替换字符串。

代码的步骤如下:

  1. 获取数据的所有字段,通过调用data.getClass().getDeclaredFields()方法获得一个Field数组。
  2. 遍历字段数组,对每个字段进行处理。
  3. 判断字段是否带有@Sensitive注解,通过调用field.isAnnotationPresent(Sensitive.class)方法进行判断。
  4. 如果字段带有@Sensitive注解,则将字段设置为可访问,通过调用field.setAccessible(true)方法实现。
  5. 获取字段上的@Sensitive注解,通过调用field.getAnnotation(Sensitive.class)方法获取注解对象。
  6. 获取注解对象中的脱敏规则,通过调用sensitive.value()方法获取DesensitizationRule对象。
  7. 判断注解对象中的替换字符串是否为空,如果为空则使用脱敏规则中的默认替换字符串,通过调用sensitive.replacement().isEmpty()方法进行判断。
  8. 获取字段的值,通过调用field.get(data)方法将字段值转换为String类型。
  9. 使用脱敏规则中的正则表达式和替换字符串,通过调用value.replaceAll(rule.getRegex(), replacement)方法对字段值进行脱敏处理。
  10. 将脱敏后的值设置回字段,通过调用field.set(data, desensitizedValue)方法实现。
  11. 如果在设置字段值的过程中发生了IllegalAccessException异常,则打印异常信息
  12. 遍历完成后,返回处理后的数据。

4、调用工具类代码示例


// 用户类,用于示例
public static class UserInfo {
    //姓名脱敏
    @Sensitive( value = DesensitizationRule.NAME, replacement = "#") 
    private String name;

    //手机号脱敏
    @Sensitive(value = DesensitizationRule.PHONE)
    private String phoneNumber;

    //身份证号脱敏
    @Sensitive(value = DesensitizationRule.ID_NUMBER)
    private String idNumber;

    public UserInfo(String name, String phoneNumber, String idNumber) {
        this.name = name;
        this.phoneNumber = phoneNumber;
        this.idNumber = idNumber;
    }

    // 省略getter和setter方法


    @Override
    public String toString() {
        return "UserInfo{" +
                "name='" + name + ''' +
                ", phoneNumber='" + phoneNumber + ''' +
                ", idNumber='" + idNumber + ''' +
                '}';
    }
}
//测试类
public class TestDesensitization {

    // 示例:对用户信息进行脱敏
    public static void main(String[] args) {
      
        UserInfo userInfo = new UserInfo("张三", "13012345678", "110101199007280518");
        System.out.println("脱敏前:" + userInfo.toString());
        desensitize(userInfo);
        System.out.println("脱敏后:" + userInfo.toString());
}