Java自定义数据脱敏注解

应用场景

数据库中密文存储身份证、手机号等敏感信息时,Java需要将密文数据转换为明文并脱敏返回给前端。

脱敏方式枚举类

public enum DesensitizationTypeEnum {

    /**
     * 默认方式
     */
    DEFAULT,

    /**
     * 头部脱敏
     */
    HEAD,

    /**
     * 尾部脱敏
     */
    TAIL,

    /**
     * 中间脱敏
     */
    MIDDLE,

    /**
     * 头尾脱敏
     */
    HEAD_TAIL,

    /**
     * 全部脱敏
     */
    ALL,

    /**
     * 不脱敏,相当于没打这个注解
     */
    NONE;
}

 脱敏数据类型枚举类

@Getter
@NoArgsConstructor
public enum DesensitizationDataTypeEnum {

    /**
     * 用户ID
     */
    USER_ID,

    /**
     * 中文名
     */
    CHINESE_NAME,

    /**
     * 身份证
     */
    ID_CARD,

    /**
     * 座机号
     */
    FIXED_PHONE,

    /**
     * 手机号
     */
    MOBILE_PHONE,

    /**
     * 地址
     */
    ADDRESS,

    /**
     * 电子邮件
     */
    EMAIL,

    /**
     * 密码
     */
    PASSWORD,

    /**
     * 车牌
     */
    CAR_LICENSE,

    /**
     * 银行卡号
     */
    BANK_CARD,

    /**
     * 其他
     */
    OTHER;

}

脱敏工具类

public class DesensitizationUtil {
    public DesensitizationUtil() {
    }

    public static String desensitized(CharSequence str, DesensitizationDataTypeEnum desensitizedType) {
        if (StrUtil.isBlank(str)) {
            return "";
        } else {
            String newStr = String.valueOf(str);
            switch (desensitizedType.ordinal()) {
                case 1:
                    newStr = String.valueOf(userId());
                    break;
                case 2:
                    newStr = chineseName(String.valueOf(str));
                    break;
                case 3:
                    newStr = idCardNum(String.valueOf(str), 1, 2);
                    break;
                case 4:
                    newStr = fixedPhone(String.valueOf(str));
                    break;
                case 5:
                    newStr = mobilePhone(String.valueOf(str));
                    break;
                case 6:
                    newStr = address(String.valueOf(str));
                    break;
                case 7:
                    newStr = email(String.valueOf(str));
                    break;
                case 8:
                    newStr = password(String.valueOf(str));
                    break;
                case 9:
                    newStr = carLicense(String.valueOf(str));
                    break;
                case 10:
                    newStr = bankCard(String.valueOf(str));
            }

            return newStr;
        }
    }

    public static Long userId() {
        return 0L;
    }

    /**
     * 【中文姓名】只显示第一个汉字,其他隐藏为2个星号,比如:李**
     *
     * @param fullName 姓名
     * @return 脱敏后的姓名
     */
    public static String chineseName(String fullName) {
        return StrUtil.isBlank(fullName) ? "" : StrUtil.hide(fullName, 1, fullName.length());
    }

    /**
     * 【身份证号】显示前1位和后2位,其他隐藏。共计18位或者15位,比如:1*****************1
     *
     * @param idCardNum 身份证号
     * @param front     保留:前面的front位数;从1开始
     * @param end       保留:后面的end位数;从1开始
     * @return 脱敏后的身份证号
     */
    public static String idCardNum(String idCardNum, int front, int end) {
        if (StrUtil.isBlank(idCardNum)) {
            return "";
        } else if (front + end > idCardNum.length()) {
            return "";
        } else {
            return front >= 0 && end >= 0 ? StrUtil.hide(idCardNum, front, idCardNum.length() - end) : "";
        }
    }

    /**
     * 【固定电话】 前四位,后两位
     *
     * @param num 固定电话
     * @return 脱敏后的固定电话
     */
    public static String fixedPhone(String num) {
        return StrUtil.isBlank(num) ? "" : StrUtil.hide(num, 4, num.length() - 2);
    }

    /**
     * 【手机号码】前三位,后四位,其他隐藏,比如:138******1234
     *
     * @param num 手机号码
     * @return 脱敏后的手机号码
     */
    public static String mobilePhone(String num) {
        return StrUtil.isBlank(num) ? "" : StrUtil.hide(num, 3, num.length() - 4);
    }

    /**
     * 【地址】只显示到地区的三分之二,尾部脱敏,不显示详细地址,比如:北京市海淀区****
     *
     * @param address 地址
     * @return 脱敏后的地址
     */
    public static String address(String address) {
        if (StrUtil.isBlank(address)) {
            return "";
        } else {
            // 地址尾部脱敏三分之一
            int length = address.length();
            int end = length / 3;
            return StrUtil.hide(address, length - end, length);
        }
    }

    /**
     * 【电子邮箱】邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@126.com
     *
     * @param email 邮箱
     * @return 脱敏后的邮箱
     */
    public static String email(String email) {
        if (StrUtil.isBlank(email)) {
            return "";
        } else {
            int index = StrUtil.indexOf(email, '@');
            return index <= 1 ? email : StrUtil.hide(email, 1, index);
        }
    }

    /**
     * 【密码】密码的全部字符都用*代替,比如:******
     *
     * @param password 密码
     * @return 脱敏后的密码
     */
    public static String password(String password) {
        return StrUtil.isBlank(password) ? "" : StrUtil.repeat('*', password.length());
    }

    /**
     * 【中国车牌】车牌中间用*代替 eg1:null -》 "" eg1:"" -》 "" eg3:苏D40000 -》 苏D4***0 eg4:陕A12345D -》 陕A1****D eg5:京A123 -》 京A123 如果是错误的车牌,不处理
     *
     * @param carLicense 完整的车牌号
     * @return 脱敏后的车牌号
     */
    public static String carLicense(String carLicense) {
        if (StrUtil.isBlank(carLicense)) {
            return "";
        } else {
            if (carLicense.length() == 7) {
                carLicense = StrUtil.hide(carLicense, 3, 6);
            } else if (carLicense.length() == 8) {
                carLicense = StrUtil.hide(carLicense, 3, 7);
            }

            return carLicense;
        }
    }

    /**
     * 银行卡号脱敏 eg: 1101 **** **** **** 3256
     *
     * @param bankCardNo 银行卡号
     * @return 脱敏后的银行卡号
     */
    public static String bankCard(String bankCardNo) {
        if (StrUtil.isBlank(bankCardNo)) {
            return bankCardNo;
        } else {
            bankCardNo = StrUtil.trim(bankCardNo);
            if (bankCardNo.length() < 9) {
                return bankCardNo;
            } else {
                int length = bankCardNo.length();
                int midLength = length - 8;
                StringBuilder buf = new StringBuilder();
                buf.append(bankCardNo, 0, 4);

                for (int i = 0; i < midLength; ++i) {
                    if (i % 4 == 0) {
                        buf.append(' ');
                    }

                    buf.append('*');
                }

                buf.append(' ').append(bankCardNo, length - 4, length);
                return buf.toString();
            }
        }
    }
}

脱敏注解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizationSerializer.class)
public @interface Desensitization {

    /**
     * 脱敏数据类型
     *
     * @return
     */
    DesensitizationDataTypeEnum dataType();

    /**
     * 是否解密,默认false
     */
    boolean decrypt() default false;

    /**
     * 脱敏类型
     *
     * @return
     */
    DesensitizationTypeEnum type() default DesensitizationTypeEnum.DEFAULT;

    /**
     * 尾部不脱敏的长度,默认1,当type为HEAD或HEAD_TAIL时使用
     */
    int tailNoMaskLen() default 1;

    /**
     * 头部不脱敏的长度,默认1,当type为TAIL或HEAD_TAIL时使用
     */
    int headNoMaskLen() default 1;

    /**
     * 中间不脱敏的长度,默认1,当type为MIDDLE时使用
     */
    int middleNoMaskLen() default 1;

    /**
     * 脱敏字符,默认*
     */
    char maskCode() default '*';

}

脱敏序列化器

@NoArgsConstructor
public class DesensitizationSerializer extends JsonSerializer<String> implements ContextualSerializer {

    private Desensitization desensitization;

    public DesensitizationSerializer(Desensitization desensitization) {
        this.desensitization = desensitization;
    }

    @Override
    public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString(desensitize(s));
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        if (beanProperty != null) {
            if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
                Desensitization desensitization = beanProperty.getAnnotation(Desensitization.class);
                if (desensitization == null) {
                    desensitization = beanProperty.getContextAnnotation(Desensitization.class);
                }
                if (desensitization != null) {
                    return new DesensitizationSerializer(desensitization);
                }
            }
            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
        }
        return serializerProvider.findNullValueSerializer(null);
    }

    /**
     * 脱敏处理
     */
    private String desensitize(String s) {
        if (StrUtil.isNotBlank(s)) {
            DesensitizationDataTypeEnum dataType = desensitization.dataType();
            DesensitizationTypeEnum type = desensitization.type();
            // 解密处理
            boolean decrypt = desensitization.decrypt();
            if (decrypt) {
                String value = PasswordUtil.decrypt(s);
                // 正则匹配,如果匹配到身份证或手机号,则返回解密后的值,否则返回原值
                if (ReUtil.contains(ReConstant.ID_CARD, value) || ReUtil.contains(ReConstant.MOBILE, value)) {
                    s = value;
                }
            }
            switch (type) {
                case DEFAULT:
                    // 默认方式,根据dataType自动选择脱敏方式
                    s = autoDesensitize(s, dataType);
                    break;
                case HEAD:
                    // 头部脱敏
                    s = headDesensitize(s);
                    break;
                case TAIL:
                    // 尾部脱敏
                    s = tailDesensitize(s);
                    break;
                case MIDDLE:
                    s = middleDesensitize(s);
                    break;
                case HEAD_TAIL:
                    s = headTailDesensitize(s);
                    break;
                case ALL:
                    s = allDesensitize(s);
                    break;
                case NONE:
                    // 不做脱敏
                    break;
                default:
            }
        }
        return s;
    }

    /**
     * 全部脱敏
     */
    private String allDesensitize(String s) {
        return StrUtil.fillBefore(s, desensitization.maskCode(), s.length());
    }

    /**
     * 头尾脱敏
     */
    private String headTailDesensitize(String s) {
        int middleNoMaskLen = desensitization.middleNoMaskLen();
        if (middleNoMaskLen >= s.length()) {
            // 如果中间不脱敏的长度大于等于字符串的长度,不进行脱敏
            return s;
        }
        int len = s.length() - middleNoMaskLen;
        // 头部脱敏
        int headStart = 0;
        int headEnd = len / 2;
        s = StrUtil.replace(s, headStart, headEnd, desensitization.maskCode());
        // 尾部脱敏
        int tailStart = s.length() - (len - len / 2);
        int tailEnd = s.length();
        return StrUtil.replace(s, tailStart, tailEnd, desensitization.maskCode());
    }

    /**
     * 中间脱敏
     */
    private String middleDesensitize(String s) {
        int headNoMaskLen = desensitization.headNoMaskLen();
        int tailNoMaskLen = desensitization.tailNoMaskLen();
        if (headNoMaskLen + tailNoMaskLen >= s.length()) {
            // 如果头部不脱敏的长度+尾部不脱敏长度 大于等于字符串的长度,不进行脱敏
            return s;
        }
        int start = headNoMaskLen;
        int end = s.length() - tailNoMaskLen;
        return StrUtil.replace(s, start, end, desensitization.maskCode());
    }

    /**
     * 尾部脱敏
     */
    private String tailDesensitize(String s) {
        int headNoMaskLen = desensitization.headNoMaskLen();
        if (headNoMaskLen >= s.length()) {
            // 如果头部不脱敏的长度大于等于字符串的长度,不进行脱敏
            return s;
        }
        int start = headNoMaskLen;
        int end = s.length();
        return StrUtil.replace(s, start, end, desensitization.maskCode());
    }

    /**
     * 头部脱敏
     */
    private String headDesensitize(String s) {
        int tailNoMaskLen = desensitization.tailNoMaskLen();
        if (tailNoMaskLen >= s.length()) {
            // 如果尾部不脱敏的长度大于等于字符串的长度,不进行脱敏
            return s;
        }
        int start = 0;
        int end = s.length() - tailNoMaskLen;
        return StrUtil.replace(s, start, end, desensitization.maskCode());
    }

    /**
     * 根据数据类型自动脱敏
     */
    private String autoDesensitize(String s, DesensitizationDataTypeEnum dataType) {
        switch (dataType) {
            case CHINESE_NAME:
                s = DesensitizationUtil.chineseName(s);
                break;
            case FIXED_PHONE:
                s = DesensitizationUtil.fixedPhone(s);
                break;
            case MOBILE_PHONE:
                s = DesensitizationUtil.mobilePhone(s);
                break;
            case ADDRESS:
                s = DesensitizationUtil.address(s);
                break;
            case PASSWORD:
                s = DesensitizationUtil.password(s);
                break;
            case BANK_CARD:
                s = DesensitizationUtil.bankCard(s);
                break;
            case EMAIL:
                s = DesensitizationUtil.email(s);
                break;
            case ID_CARD:
                s = DesensitizationUtil.idCardNum(s, 6, 4);
                break;
            case OTHER:
                // 其他类型的不支持以默认方式脱敏,直接返回
                break;
            default:
        }
        return s;
    }
}

 返回对象


    @ApiModelProperty(value = "证件号码")
    @Desensitization(dataType = DesensitizationDataTypeEnum.ID_CARD, decrypt = true)
    @Encrypt // 前端传参时对数据进行加密匹配数据库密文身份证
    private String idcard;

    @ApiModelProperty(value = "联系电话: 一般登记手机密码,将在找回密码、修改密码时用于发送验证短信。")
    @Desensitization(dataType = DesensitizationDataTypeEnum.MOBILE_PHONE, decrypt = true)
    @Encrypt
    private String tel;

    @ApiModelProperty(value = "住址")
    @Desensitization(dataType = DesensitizationDataTypeEnum.ADDRESS)
    private String address;

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Boot中实现自定义注解脱敏,可以按照以下步骤进行操作: 1. 创建一个自定义注解类,在该类上使用`@Target`、`@Retention`等元注解进行注解的定义。注解中可以包含一些属性,用来指定脱敏的方式或类型。 引用 2. 在需要进行脱敏的字段上,使用刚刚定义的自定义注解进行标记。可以在实体类的字段上加上该注解,或者在需要脱敏的方法上加上该注解。 引用 3. 创建一个拦截器或者切面类,在请求进入时,通过反射机制获取到带有自定义注解的字段,并对其进行脱敏处理。脱敏的方式可以根据注解中指定的类型来进行处理。 引用 4. 在Spring Boot的配置文件(例如application.yml)中配置相关信息,如服务器端口、数据库连接信息等。 引用 通过以上步骤,就可以在Spring Boot项目中实现自定义注解脱敏的功能了。自定义注解可以帮助我们标记需要脱敏的字段,然后在拦截器或切面中实现脱敏逻辑,保护敏感数据的安全性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [springboot 自定义注解实现数据脱敏](https://blog.csdn.net/a2365900668/article/details/120306728)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值