定义一个枚举,标明字段类型

@Getter
@AllArgsConstructor
public enum ReadableSensitiveTypeEnum {

    /**
     * 身份证编号
     */
    ID_CARD("身份证"),

    /**
     * 地址/住址
     */
    ADDRESS("地址"),

    /**
     * 姓名
     */
    NAME("姓名"),

    /**
     * 手机号
     */
    PHONE("手机号"),

    /**
     * 手机号
     */
    EMAIL("邮箱"),

    /**
     * 银行卡号
     */
    BANK_CARD_NO("银行卡号");

    private String desc;

}

定义注解,用于标注实体类需要脱敏的字段

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface ReadableSensitiveVerify {

    ReadableSensitiveTypeEnum value();

}

例如返回值的实体类

@Data
@EqualsAndHashCode()
@ApiModel(value="CustomerInfoListVo对象", description="个人客户列表信息")
public class CustomerInfoListVo implements Serializable {

    @ApiModelProperty(value = "记录id")
    private Integer id;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
    @ApiModelProperty(value = "创建时间")
    private Date createTime;

    @ApiModelProperty(value = "客户名称")
    @ReadableSensitiveVerify(ReadableSensitiveTypeEnum.NAME)
    private String realName;

    @ApiModelProperty(value = "手机号码")
    @ReadableSensitiveVerify(ReadableSensitiveTypeEnum.PHONE)
    private String phone;

    @ApiModelProperty(value = "客户身份证号")
    @ReadableSensitiveVerify(ReadableSensitiveTypeEnum.ID_CARD)
    private String idCarNumber;

    @ApiModelProperty(value = "性别")
    private String gender;

    @ApiModelProperty(value = "出生日期")
    @JsonFormat(pattern = "yyyy-MM-dd",timezone="GMT+8")
    private Date birthDate;

    @ApiModelProperty(value = "客户编号")
    private String customerCode;

    @ApiModelProperty(value = "邮箱")
    private String email;

    @ApiModelProperty(value = "客户经理")
    private String customerManager;

    @ApiModelProperty(value = "微信号")
    private String weChat;

    @ApiModelProperty(value = "拉黑时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
    private Date blockingTime;

    @ApiModelProperty(value = "拉黑说明")
    private String blockInstructions;

    @ApiModelProperty(value = "意向等级")
    private String interestingGrade;
}

脱敏工具类

/**
* @description: 脱敏工具类
* @return:
* @author: Ming
* @time: 2022/6/22
*/
public class DesensitizationUtils {

    /**
    * @description: 名字脱敏
     * 脱敏规则: 隐藏中中间部分,比如:李某人 置换为 李*人 , 李某置换为 *某,司徒司翘置换为 司**翘
    * @return:
    * @author: Ming
    * @time: 2022/6/22
    */
    public static String desensitizedName(String fullName){
        if (!Strings.isNullOrEmpty(fullName)) {
            int length = fullName.length();
            if(length == 2){
                return "*".concat(fullName.substring(1));
            }else if(length == 3){
                return StringUtils.left(fullName,1).concat("*").concat(StringUtils.right(fullName,1));
            }else if(length > 3){
                return StringUtils.left(fullName,1).concat(generateAsterisk(fullName.substring(1,length-1).length())).concat(StringUtils.right(fullName,1));
            }else {
                return fullName;
            }
        }
        return fullName;
    }


    /**
    * @description: 手机号脱敏,脱敏规则: 保留前三后四, 比如15566026528置换为155****6528
    * @return:
    * @author: Ming
    * @time: 2022/6/22
    */
    public static String desensitizedPhoneNumber(String phoneNumber){
        if(StringUtils.isNotEmpty(phoneNumber)){
            int length = phoneNumber.length();
            if(length == 11){
                return phoneNumber.replaceAll("(\\w{3})\\w*(\\w{4})", "$1****$2");
            }else if(length > 2){
                return StringUtils.left(phoneNumber,1).concat(generateAsterisk(phoneNumber.substring(1,length-2).length())).concat(StringUtils.right(phoneNumber,1));
            }else {
                return phoneNumber;
            }
        }
        return phoneNumber;
    }


    /**
    * @description: 身份证脱敏
     * 脱敏规则: 保留前六后三, 适用于15位和18位身份证号:
     * 原身份证号(15位):210122198401187,脱敏后的身份证号:210122******187
     * 原身份证号(18位):210122198401187672,脱敏后的身份证号:210122*********672
    * @return:
    * @author: Ming
    * @time: 2022/6/22
    */
    public static String desensitizedIdNumber(String idNumber){
        if (!Strings.isNullOrEmpty(idNumber)) {
            int length = idNumber.length();
            if (length == 15){
               return idNumber.replaceAll("(\\w{6})\\w*(\\w{3})", "$1******$2");
            }else if (length == 18){
                return idNumber.replaceAll("(\\w{6})\\w*(\\w{3})", "$1*********$2");
            }else if(length > 9){
                return StringUtils.left(idNumber,6).concat(generateAsterisk(idNumber.substring(6,length-3).length())).concat(StringUtils.right(idNumber,3));
            }
        }
        return idNumber;
    }


    /**
    * @description: 电子邮箱脱敏,脱敏规则:电子邮箱隐藏@前面的3个字符
    * @return:
    * @author: Ming
    * @time: 2022/6/22
    */
    public static String desensitizationEmail(String email) {
        if (StringUtils.isEmpty(email)) {
            return email;
        }
        String encrypt = email.replaceAll("(\\w+)\\w{3}@(\\w+)", "$1***@$2");
        if (email.equalsIgnoreCase(encrypt)) {
            encrypt = email.replaceAll("(\\w*)\\w{1}@(\\w+)", "$1*@$2");
        }
        return encrypt;
    }


    /**
    * @description: 地址脱敏,脱敏规则:从第4位开始隐藏,隐藏8位
    * @return:
    * @author: Ming
    * @time: 2022/6/22
    */
    public static String desensitizedAddress(String address){
        if (!Strings.isNullOrEmpty(address)) {
            int length = address.length();
            if(length > 4 && length <= 12){
                return StringUtils.left(address, 3).concat(generateAsterisk(address.substring(3).length()));
            }else if(length > 12){
                return StringUtils.left(address,3).concat("********").concat(address.substring(11));
            }else {
                return address;
            }
        }
        return address;
    }


    /**
    * @description: 银行账号脱敏, 脱敏规则:银行账号保留前六后四
    * @return:
    * @author: Ming
    * @time: 2022/6/23
    */
    public static String desensitizedAddressBankCardNum(String acctNo) {
        if (StringUtils.isNotEmpty(acctNo)) {
            String regex = "(\\w{6})(.*)(\\w{4})";
            Matcher m = Pattern.compile(regex).matcher(acctNo);
            if (m.find()) {
                String rep = m.group(2);
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < rep.length(); i++) {
                    sb.append("*");
                }
                acctNo = acctNo.replaceAll(rep, sb.toString());
            }
        }
        return acctNo;
    }


    /**
    * @description: 返回指定长度*字符串
    * @return:
    * @author: Ming
    * @time: 2022/6/22
    */
    private static String generateAsterisk(int length){
        String result = "";
        for (int i = 0; i < length; i++) {
            result += "*";
        }
        return result;
    }
}

aop实现脱敏

/**
* @description: 返回值数据脱敏处理aop
* @return:
* @author: Ming
* @time: 2022/6/22
*/
@Slf4j
@Component
@Aspect
public class DesensitizationAspect {

    @Autowired
    private UserUtils userUtils;

    /**
    * @description: 切入点
    * @return:
    * @author: Ming
    * @time: 2022/6/22
    */
    @Pointcut("execution(* com.yptx.financialsystem.*.controller.*.*(..))")
    public void pointCut() {
    }


    /**
    * @description: 返回值处理
    * @return:
    * @author: Ming
    * @time: 2022/6/22
    */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        SysUser user = userUtils.getSysUser();
        log.info("user: {}",user);
        //typeEnums表示用户拥有哪些字段的查看权限,没有的就脱敏处理
        List<ReadableSensitiveTypeEnum> typeEnums = new ArrayList<>();
        if(user != null){
            typeEnums = user.getReadableSensitives() == null ? typeEnums : user.getReadableSensitives();
        }
        log.info("typeEnums: {}",typeEnums);
        Object obj = proceedingJoinPoint.proceed();
        if (obj == null || isPrimitive(obj.getClass())) {
            return obj;
        }
        dealData(obj,typeEnums);
        return obj;
    }

    /**
    * @description: 基本数据类型和String类型判断
    * @return:
    * @author: Ming
    * @time: 2022/6/23
    */
    private boolean isPrimitive(Class<?> clz) {
        try {
            if (String.class.isAssignableFrom(clz) || clz.isPrimitive()) {
                return true;
            } else {
                return ((Class) clz.getField("TYPE").get(null)).isPrimitive();
            }
        } catch (Exception e) {
            return false;
        }
    }


    /**
    * @description: 数据处理
    * @return:
    * @author: Ming
    * @time: 2022/6/23
    */
    private void dealData(Object obj,List<ReadableSensitiveTypeEnum> typeEnums){
        if (null == obj) {return;}
        if (obj.getClass().isPrimitive()) {return;}// 是否是接口
        if (obj.getClass().isInterface()) {return;}
        Object data = ((ResponseJson) obj).getObj();
        if(data != null){
            Class<?> clazz = data.getClass();
            if (clazz.equals(Page.class)) {
                Page page = (Page) data;
                List<?> record = page.getRecords();
                for (Object o : record) {
                    Field[] fields = o.getClass().getDeclaredFields();
                    replace(fields,o,typeEnums);
                }
            }else {
                Field[] fields = clazz.getDeclaredFields();
                replace(fields,data,typeEnums);
            }
        }
    }


    /**
    * @description: 脱敏敏感字段
    * @return:
    * @author: Ming
    * @time: 2022/6/23
    */
    private void replace(Field[] fields,Object o,List<ReadableSensitiveTypeEnum> typeEnums){
        try {
            for (Field f : fields) {
                if(f != null){
                    //设置private字段可访问
                    f.setAccessible(true);
                    //处理自定义vo作为属性(属性类型非自身类对象类型的属性)
                    CustomEntityDesensitizationVerify custom = f.getAnnotation(CustomEntityDesensitizationVerify.class);
                    if(custom != null){
                        Object customEntity = f.get(o);
                        Field[] entityFiled = customEntity.getClass().getDeclaredFields();
                        replace(entityFiled, customEntity,typeEnums);
                    }
                    //处理list属性
                    Class<?> curFieldType = f.getType();
                    if (curFieldType.equals(List.class)) {
                        List<?> record = (List<?>) f.get(o);
                        if(record != null && !record.isEmpty() ){
                            for (Object obj :record) {
                                Field[] ff= obj.getClass().getDeclaredFields();
                                replace(ff,obj,typeEnums);
                            }
                        }
                    }
                    //处理普通字符串字段
                    ReadableSensitiveVerify annotation = f.getAnnotation(ReadableSensitiveVerify.class);
                    if(annotation != null){
                        f.getType();
                        String valueStr = (String) f.get(o);
                        if(StringUtils.isNotEmpty(valueStr)){
                            ReadableSensitiveTypeEnum type = annotation.value();
                            if(type.equals(ReadableSensitiveTypeEnum.NAME) && !typeEnums.contains(type)){
                                f.set(o, DesensitizationUtils.desensitizedName(valueStr));
                            }
                            if(type.equals(ReadableSensitiveTypeEnum.ID_CARD) && !typeEnums.contains(type) ){
                                f.set(o, DesensitizationUtils.desensitizedIdNumber(valueStr));
                            }
                            if(type.equals(ReadableSensitiveTypeEnum.ADDRESS) && !typeEnums.contains(type)){
                                f.set(o, DesensitizationUtils.desensitizedAddress(valueStr));
                            }
                            if(type.equals(ReadableSensitiveTypeEnum.PHONE) && !typeEnums.contains(type)){
                                f.set(o, DesensitizationUtils.desensitizedPhoneNumber(valueStr));
                            }
                            if(type.equals(ReadableSensitiveTypeEnum.BANK_CARD_NO) && !typeEnums.contains(type)){
                                f.set(o, DesensitizationUtils.desensitizedAddressBankCardNum(valueStr));
                            }
                            if(type.equals(ReadableSensitiveTypeEnum.EMAIL) && !typeEnums.contains(type)){
                                f.set(o, DesensitizationUtils.desensitizationEmail(valueStr));
                            }
                        }
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

效果

SpringBoot敏感信息泄露漏洞 整改建议 springboot 数据脱敏_spring boot


有些稍微复杂的返回值,比如自定义的实体类作为属性嵌套,Page嵌套等,例如:

@Data
@EqualsAndHashCode()
@ApiModel(value="CustomerFamilyVo对象", description="个人客户家庭基础信息Vo")
public class CustomerFamilyVo implements Serializable {

    @ApiModelProperty(value = "记录id")
    private Integer id;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
    @ApiModelProperty(value = "创建时间")
    private Date createTime;

    @ApiModelProperty(value = "家庭月收入")
    private BigDecimal familyMonthlyIncome;

    @ApiModelProperty(value = "家庭月支出")
    private BigDecimal familyMonthlyExpenditure;

    @ApiModelProperty(value = "主要供养人数")
    private Integer providePeopleSize;

    @ApiModelProperty(value = "家庭资料")
    private String familyInformation;

    @ApiModelProperty(value = "客户基础信息主表Id")
    private Integer customerInfoId;

    @ApiModelProperty(value = "个人客户家庭成员基础信息")
    @CustomEntityDesensitizationVerify
    IPage<CustomerFamilyMembersVo> familyMembersVoIPage;
}
@Data
@EqualsAndHashCode()
@ApiModel(value="CustomerFamilyMembersVo对象", description="个人客户家庭成员基础信息Vo")
public class CustomerFamilyMembersVo implements Serializable {

    @ApiModelProperty(value = "记录id")
    private Integer id;

    @ApiModelProperty(value = "家庭成员真实姓名")
    @ReadableSensitiveVerify(ReadableSensitiveTypeEnum.NAME)
    private String realName;

    @ApiModelProperty(value = "家庭成员与客户的关系(配偶、父亲、母亲、子女)")
    private FamilyRelationshipEnums relationship;

    @ApiModelProperty(value = "家庭成员联系方式")
    @ReadableSensitiveVerify(ReadableSensitiveTypeEnum.PHONE)
    private String phone;

    @ApiModelProperty(value = "身份证号码")
    @ReadableSensitiveVerify(ReadableSensitiveTypeEnum.ID_CARD)
    private String idCarNumber;

    @ApiModelProperty(value = "身份证图片(正面)")
    private String idCarPicFront;

    @ApiModelProperty(value = "身份证图片(反面)")
    private String idCarPicReverse;

    @ApiModelProperty(value = "居住地址")
    @ReadableSensitiveVerify(ReadableSensitiveTypeEnum.ADDRESS)
    private String address;

    @ApiModelProperty(value = "家庭基础信息主表Id")
    private Integer customerFamilyId;

    @ApiModelProperty(value = "客户基础信息主表Id")
    private Integer customerInfoId;
}

再加个注解表明是自定义的对象作为属性字段

/**
* @description: 标注返回值中属性为自定义非集合类型实体类对象(适用于:属性类型非当前类的对象类型)
* @return:
* @author: Ming
* @time: 2022/6/23
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface CustomEntityDesensitizationVerify {
}

效果

SpringBoot敏感信息泄露漏洞 整改建议 springboot 数据脱敏_java_02