Spring Boot基于注解方式处理接口数据脱敏

12 篇文章 0 订阅
4 篇文章 0 订阅

1.定义注解

创建Spring Boot项目添加以下依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-core</artifactId>
            <version>5.7.22</version>
        </dependency>
    </dependencies>

然后定义Sensitive 注解,@JacksonAnnotationsInside的作用是将@JacksonAnnotation标注的注解作为一个组合注解,这里使用@JacksonAnnotationsInside在@Sensitive 注解中组合@JsonSerialize注解,这样就可以使用JsonSerialize的功能,并且可以拓展自定义属性和隐式得指定自定义序列化器SensitiveSerializer了。

@Target({ElementType.FIELD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveSerializer.class)
public @interface Sensitive {
    /**
     * 脱敏的类型,默认手机号
     * @return
     */
    Type value();

    /**
     * CUSTOM_HIDE/CUSTOM_OVERLAY 时生效
     * 开始位置(包含)
     * @return
     */
    int startInclude() default 0;

    /**
     * CUSTOM_HIDE/CUSTOM_OVERLAY 时生效
     * 结束位置(不包含)
     * @return
     */
    int endExclude() default 0;


    /**
     * CUSTOM_OVERLAY 时生效,*重复的次数
     * @return
     */
    int overlayRepeat() default 4;


    /**
     * Enumeration used with {@link Sensitive}
     */
    public enum Type {
        /**
         * 手机号
         */
        MOBILE,
        /**
         * 中文名
         */
        CHINESE_NAME,
        /**
         * 身份证号
         */
        ID_CARD,
        /**
         * 座机号
         */
        FIXED_PHONE,
        /**
         * 地址
         */
        ADDRESS,
        /**
         * 电子邮件
         */
        EMAIL,
        /**
         * 银行卡
         */
        BANK_CARD,
        /**
         * 自定义,有多少个字符替换成多少个*
         * e.g: startInclude=3,endExclude=7,隐藏第3个到第7个的字符
         */
        CUSTOM_HIDE,
        /**
         *保留方式隐藏
         * e.g: startInclude=3,endExclude=4 ,保留前面3个和后面的4个
         */
        CUSTOM_RETAIN_HIDE,
        /**
         * 自定义,只替换成指定个*
         */
        CUSTOM_OVERLAY,
    }

}

2. 脱敏工具类

创建脱敏工具类SensitiveUtil ,统一封装数据脱敏的方法。

public class SensitiveUtil {


    /**
     * [手机号码] 前3位后4位明码,中间4位掩码用****显示,如138****0000
     * @param mobile 手机号码
     * @return
     */
    public static String handlerMobile(String mobile) {
        if(StringUtil.isEmpty(mobile)){
            return null;
        }
         return hide(mobile,3,mobile.length() - 4);
    }

    /**
     * [手机号码] 只显示后四位, 如:*8856
     * @param phone 手机号码
     * @return
     */
    public static String handlerPhone(String phone) {
        if(StringUtil.isEmpty(phone)){
            return null;
        }
        return overlay(phone,StringPool.ASTERISK,1,0,phone.length() - 4);
    }


    /**
     * [身份证号] 前6位后4位明码,中间掩码用***显示,如511623********0537
     * @param idNum
     * @return
     */
    public static String handlerIdCard(String idNum) {
        if(StringUtil.isEmpty(idNum)){
            return null;
        }
        return hide(idNum,6,idNum.length() - 4);
    }

    /**
     * [银行卡] 前6位后4位明码,中间部分****代替,如622848*********5579
     * @param cardNum
     * @return
     */
    public static String handlerBankCard(String cardNum) {
        if(StringUtil.isEmpty(cardNum)){
            return null;
        }
        return hide(cardNum,6,cardNum.length() - 4);
    }

    /**
     * [地址] 只显示到地区,不显示详细地址;我们要对个人信息增强保护<例子:广东省广州市天河区****>
     * @param address
     */
    public static String handlerAddress(String address) {
        if(StringUtil.isEmpty(address)){
            return null;
        }
        return overlay(address,StringPool.ASTERISK,4,9,address.length());
    }


    /**
     * [用户名] 只显示第一位 <例子:黄**>
     * @param username
     * @return
     */
    public static String handlerUsername(String username) {
        if(StringUtil.isEmpty(username)){
            return null;
        }
        return hide(username,1,username.length());
    }

    /**
     * [固定电话] 后四位,其他隐藏<例子:****1234>
     */
    public static String handlerFixedPhone(final String num) {
        if(StringUtil.isEmpty(num)){
            return null;
        }
        return overlay(num, StringPool.ASTERISK,4, 0,num.length()-4);
    }

    /**
     * [电子邮箱] 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示<例子:g**@163.com>
     * @param email
     * @return
     */
    public static String handlerEmail(final String email) {
        if(StringUtil.isEmpty(email)){
            return null;
        }
        final int index = StringUtil.indexOf(email, StringPool.AT_CHAR);
        if (index <= 1) {
            return email;
        } else {
            return hide(email, 1, index);
        }
    }

    /**
     * 用另一个字符串覆盖一个字符串的一部分
     * @param str  需要替换的字符串
     * @param overlay  将被替换成的字符串
     * @param overlayRepeat  overlay重复的次数
     * @param start  开始位置
     * @param end  结束位置
     * @return
     */
    public static String overlay(String str, String overlay,int overlayRepeat, int start, int end) {
        if (StringUtil.isEmpty(str)) {
            return StringUtil.str(str);
        }
        if (StringUtil.isEmpty(overlay)) {
            overlay = StringPool.EMPTY;
        }
        final int len = str.length();
        if (start < 0) {
            start = 0;
        }
        if (start > len) {
            start = len;
        }
        if (end < 0) {
            end = 0;
        }
        if (end > len) {
            end = len;
        }
        if (start > end) {
            final int temp = start;
            start = end;
            end = temp;
        }
        return str.substring(0, start) + StringUtil.repeat(overlay, overlayRepeat) + str.substring(end);
    }

    /**
     * 替换指定字符串的指定区间内字符为"*"
     *
     * @param str 字符串
     * @param startInclude 开始位置(包含)
     * @param endExclude 结束位置(不包含)
     * @return 替换后的字符串
     */
    public static String hide(String str, int startInclude, int endExclude) {
        return hide(str, startInclude, endExclude,StringPool.ASTERISK_CHAR);

    }

    /**
     * 替换指定字符串的指定区间内字符为"*"
     *
     * @param str 字符串
     * @param startInclude 开始位置(包含)
     * @param endExclude 结束位置(不包含)
     * @param replacedChar
     * @return 替换后的字符串
     */
    public static String hide(String str, int startInclude, int endExclude, char replacedChar) {
        if (StringUtil.isEmpty(str)) {
            return StringUtil.str(str);
        }
        final int strLength = str.length();
        if (startInclude > strLength) {
            return StringUtil.str(str);
        }
        if (endExclude > strLength) {
            endExclude = strLength;
        }
        if (startInclude > endExclude) {
            // 如果起始位置大于结束位置,不替换
            return StringUtil.str(str);
        }

        final char[] chars = new char[strLength];
        for (int i = 0; i < strLength; i++) {
            if (i >= startInclude && i < endExclude) {
                chars[i] = replacedChar;
            } else {
                chars[i] = str.charAt(i);
            }
        }
        return new String(chars);
    }

}

3. 自定义Jackson序列化器

参考Jackson自带的StringSerializer,继承JsonSerializer,重写serialize方法,自定义Jackson序列化器SensitiveSerializer针对String类型数据进行数据脱敏处理。

public class SensitiveSerializer extends JsonSerializer<String> implements ContextualSerializer {
    private Sensitive.Type type;
    private int startInclude;
    private int endExclude;
    private int overlayRepeat;

    public SensitiveSerializer() {}
    public SensitiveSerializer(final Sensitive sensitive) {
        this.type = sensitive.value();
        this.startInclude = sensitive.startInclude();
        this.endExclude = sensitive.endExclude();
        this.overlayRepeat = sensitive.overlayRepeat();
    }


    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        if(ObjectUtil.isEmpty(value)){
            gen.writeString(value);
            return;
        }else {
            switch (this.type) {
                case MOBILE:
                    gen.writeString(SensitiveUtil.handlerMobile(value));
                    break;
                case ID_CARD:
                    gen.writeString(SensitiveUtil.handlerIdCard(value));
                    break;
                case BANK_CARD:
                    gen.writeString(SensitiveUtil.handlerBankCard(value));
                    break;
                case CHINESE_NAME:
                    gen.writeString(SensitiveUtil.handlerUsername(value));
                    break;
                case FIXED_PHONE:
                    gen.writeString(SensitiveUtil.handlerFixedPhone(value));
                    break;
                case ADDRESS:
                    gen.writeString(SensitiveUtil.handlerAddress(value));
                    break;
                case EMAIL:
                    gen.writeString(SensitiveUtil.handlerEmail(value));
                    break;
                case CUSTOM_HIDE:
                    gen.writeString(SensitiveUtil.hide(value,startInclude,endExclude));
                    break;
                case CUSTOM_RETAIN_HIDE:
                    gen.writeString(SensitiveUtil.hide(value,startInclude,(value.length()-endExclude)));
                    break;
                case CUSTOM_OVERLAY:
                    gen.writeString(SensitiveUtil.overlay(value, StringPool.ASTERISK,overlayRepeat,startInclude,endExclude));
                    break;
                default:
                    gen.writeString(value);
            }
        }

    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
        if(Objects.isNull(property)){
            return prov.getDefaultNullValueSerializer();
        }
        if(Objects.equals(property.getType().getRawClass(), String.class)){
            Sensitive sensitive = property.getAnnotation(Sensitive.class);
            if (Objects.isNull(sensitive)) {
                sensitive = property.getContextAnnotation(Sensitive.class);
            }
            if (Objects.nonNull(sensitive)) {
                return new SensitiveSerializer(sensitive);
            }
        }
        return prov.findValueSerializer(property.getType(), property);
        //Sensitive sensitive = property.getAnnotation(Sensitive.class);
        //type = sensitive.value();
        //return this;
    }
}

4. 测试使用

创建实体类Student

public class Student {

    @Sensitive(value = Sensitive.Type.CHINESE_NAME)
    private String name;

    @Sensitive(value = Sensitive.Type.ID_CARD)
    private String idCard;

    @Sensitive(value = Sensitive.Type.BANK_CARD)
    private String bankCard;

    @Sensitive(value = Sensitive.Type.FIXED_PHONE)
    private String fixedPhone;

    @Sensitive(value = Sensitive.Type.ADDRESS)
    private String address;

    @Sensitive(value = Sensitive.Type.EMAIL)
    private String email;

    @Sensitive(value = Sensitive.Type.CUSTOM_RETAIN_HIDE,startInclude = 3,endExclude = 10)
    private String remark;

    public Student() {
    }

    public String getBankCard() {
        return bankCard;
    }

    public void setBankCard(String bankCard) {
        this.bankCard = bankCard;
    }

    public String getFixedPhone() {
        return fixedPhone;
    }

    public void setFixedPhone(String fixedPhone) {
        this.fixedPhone = fixedPhone;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    public Student(String name, String idCard, String bankCard, String fixedPhone, String address, String email, String remark) {
        this.name = name;
        this.idCard = idCard;
        this.bankCard = bankCard;
        this.fixedPhone = fixedPhone;
        this.address = address;
        this.email = email;
        this.remark = remark;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getIdCard() {
        return idCard;
    }

    public void setIdCard(String idCard) {
        this.idCard = idCard;
    }
}

定义接口/test/student

@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping("/student")
    public Student getStudent(){
        Student student = new Student();
        student.setName("张三三");
        student.setIdCard("44082199612054343");
        student.setBankCard("62173300255879654448");
        student.setFixedPhone("3110026");
        student.setAddress("广东省广州市天河区");
        student.setEmail("1258398545@qq.com");
        student.setRemark("sadhaonsdoasnodnaonodsn是大祭司大祭司你");
        return student;
    }
}

浏览器请求接口/test/student,查看接口返回的数据

在这里插入图片描述

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot可以通过AOP(面向切面编程)来实现数据脱敏数据脱敏是指在保持数据结构不变的情况下,对敏感数据进行加密或者替换等操作,以保护数据的安全性和隐私。在Spring Boot中,可以使用AOP来拦截需要脱敏的方法,然后对方法返回的敏感数据进行处理。 首先,我们需要定义一个脱敏注解,例如@SensitiveData,用于标记需要进行脱敏的方法或者字段。接着,我们可以自定义一个AOP的切面类,使用@Around注解来拦截带有@SensitiveData注解的方法。 在切面类中,我们可以使用反射来获取方法返回的数据,并进行脱敏处理。对于不同类型的数据,可以采取不同的脱敏策略,例如对字符串进行部分隐藏、手机号码替换为部分星号、银行卡号进行掩码等。脱敏处理完成后,可以将处理后的数据替换原始数据,并返回给调用方。 在Spring Boot的配置文件中,我们需要开启AOP的自动配置,并配置切面类的扫描路径。这样,当有方法被调用时,带有@SensitiveData注解的方法会被切面类拦截,并进行脱敏处理。 通过使用AOP实现数据脱敏,可以将脱敏的逻辑与业务逻辑分离,提高了代码的可读性和可维护性。同时,使用注解标记需要脱敏的方法,使得代码更加清晰明了。 总之,Spring Boot通过AOP可以很方便地实现数据脱敏,保护敏感数据的安全性和隐私。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值