SpringBoot 自定义注解之脱敏注解详解

目录
  • 自定义注解之脱敏注解
    • 一、脱敏后的效果
    • 二、代码
      • 1.脱敏注解
      • 2.定义脱敏类型
      • 3.敏感工具类
      • 4.脱敏序列化信息
    • 小结一下
  • 自己手写的一个高效自定义字符串脱敏注解
    • 自己写了个 仅供参考

自定义注解之脱敏注解

数据脱敏是指对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护。需求是把返回到前端的数据进行脱敏,以免造成隐私信息的泄露。

一、脱敏后的效果

这样显示很不好吧,所有信息都泄露了

这样就很好了吧

二、代码

1.脱敏注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveSerialize.class)
public @interface Sensitive {
    /**
     * 脱敏数据类型
     */
    SensitiveTypeEnum type() default SensitiveTypeEnum.CUSTOMER;
    /**
     * 前置不需要打码的长度
     */
    int prefixNoMaskLen() default 0;
    /**
     * 后置不需要打码的长度
     */
    int suffixNoMaskLen() default 0;
    /**
     * 用什么打码
     */
    String symbol() default "*";
}

2.定义脱敏类型

public enum SensitiveTypeEnum {
    /**
     * 自定义
     */
    CUSTOMER,
    /**
     * 姓名
     */
    NAME,
    /**
     * 身份证
     */
    ID_NUM,
    /**
     * 手机号码
     */
    PHONE_NUM
}

3.敏感工具类

public class DesensitizedUtils {
    /**
     * 对字符串进行脱敏操作
     *
     * @param origin          原始字符串
     * @param prefixNoMaskLen 左侧需要保留几位明文字段
     * @param suffixNoMaskLen 右侧需要保留几位明文字段
     * @param maskStr         用于遮罩的字符串, 如'*'
     * @return 脱敏后结果
     */
    public static String desValue(String origin, int prefixNoMaskLen, int suffixNoMaskLen, String maskStr) {
        if (origin == null) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0, n = origin.length(); i < n; i++) {
            if (i < prefixNoMaskLen) {
                sb.append(origin.charAt(i));
                continue;
            }
            if (i > (n - suffixNoMaskLen - 1)) {
                sb.append(origin.charAt(i));
                continue;
            }
            sb.append(maskStr);
        }
        return sb.toString();
    }
    /**
     * 【中文姓名】只显示最后一个汉字,其他隐藏为星号,比如:**梦
     *
     * @param fullName 姓名
     * @return 结果
     */
    public static String chineseName(String fullName) {
        if (fullName == null) {
            return null;
        }
        return desValue(fullName, 1, 0, "*");
    }
    /**
     * 【身份证号】显示前4位, 后2位,其他隐藏。
     *
     * @param id 身份证号码
     * @return 结果
     */
    public static String idCardNum(String id) {
        return desValue(id, 4, 2, "*");
    }
    /**
     * 【手机号码】前三位,后四位,其他隐藏。
     *
     * @param num 手机号码
     * @return 结果
     */
    public static String mobilePhone(String num) {
        return desValue(num, 3, 4, "*");
    }
}

4.脱敏序列化信息

@NoArgsConstructor
@AllArgsConstructor
public class SensitiveSerialize extends JsonSerializer<String> implements ContextualSerializer {
    /**
     * 脱敏类型
     */
    private SensitiveTypeEnum sensitiveTypeEnum;
    /**
     * 前几位不脱敏
     */
    private Integer prefixNoMaskLen;
    /**
     * 最后几位不脱敏
     */
    private Integer suffixNoMaskLen;
    /**
     * 用什么打码
     */
    private String symbol;
    @Override
    public void serialize(final String origin, final JsonGenerator jsonGenerator,
                          final SerializerProvider serializerProvider) throws IOException {
        switch (sensitiveTypeEnum) {
            case CUSTOMER:
                jsonGenerator.writeString(DesensitizedUtils.desValue(origin, prefixNoMaskLen, suffixNoMaskLen, symbol));
                break;
            case NAME:
                jsonGenerator.writeString(DesensitizedUtils.chineseName(origin));
                break;
            case ID_NUM:
                jsonGenerator.writeString(DesensitizedUtils.idCardNum(origin));
                break;
            case PHONE_NUM:
                jsonGenerator.writeString(DesensitizedUtils.mobilePhone(origin));
                break;
            default:
                throw new IllegalArgumentException("unknown sensitive type enum " + sensitiveTypeEnum);
        }
    }
    @Override
    public JsonSerializer<?> createContextual(final SerializerProvider serializerProvider,
                                              final BeanProperty beanProperty) throws JsonMappingException {
        if (beanProperty != null) {
            if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
                Sensitive sensitive = beanProperty.getAnnotation(Sensitive.class);
                if (sensitive == null) {
                    sensitive = beanProperty.getContextAnnotation(Sensitive.class);
                }
                if (sensitive != null) {
                    return new SensitiveSerialize(sensitive.type(), sensitive.prefixNoMaskLen(),
                            sensitive.suffixNoMaskLen(), sensitive.symbol());
                }
            }
            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
        }
        return serializerProvider.findNullValueSerializer(null);
    }
}

小结一下

该注解用于隐私数据的脱敏,只作用于类的属性上。该注解有四个属性,type表示脱敏数据类型(默认为CUSTOMER自定义,后面三个属性才有效),prefixNoMaskLen表示前置不需要打码的长度(默认为0),suffixNoMaskLen表示后置不需要打码的长度(默认为0),symbol表示用什么打码(默认为*)。

一般用于返回对象给前端对象中包含隐私数据例如身份证、详细地址需要进行脱敏的情况。

示例:

public class UserInfo {
    @Sensitive(type = SensitiveTypeEnum.NAME)
    private String name;
    @Sensitive(type = SensitiveTypeEnum.ID_NUM)
    private String idNum;
    @Sensitive(type = SensitiveTypeEnum.PHONE_NUM)
    private String phone;
    @Sensitive(type = SensitiveTypeEnum.CUSTOMER, prefixNoMaskLen = 3, suffixNoMaskLen = 2, symbol = "#")
    private String address;
    @Sensitive(prefixNoMaskLen = 1, suffixNoMaskLen = 2, symbol = "*")
    private String password;
}

如果还有疑问我写了个demo,可以下载下来运行看看

链接: 脱敏注解demo.

自己手写的一个高效自定义字符串脱敏注解

经理要求写一个自定义脱敏注解,百度查了一堆。都是效率比较低的

自己写了个 仅供参考

/**
 * description: 数据脱敏
 * 1、默认不传部位、不传显示*号数量时字段全部脱敏
 *
 * 原始字符串 adminis 总长度从0计算 总数6
 * index=(0,2) size = 1 下标即从0到2以内的字符标注“ * ”,size=1 则只填充一个* size 不能超过截取字符
 * index=(2,3) size =2 下标即从2到3以内的字符标注“ * ”,size=2 则只填充二个* size 不能超过截取字符
 *
 * date: 2020/3/13 15:56
 *
 * @author oakdog
 * @version 1.0
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = Desensitization.ConvertDesensitization.class)
public @interface Desensitization {
    /**
     * 	传入的下标索引
     * 	规则 第一位起始下标 第二位是结束下标 默认值6位下标
     **/
    int[] index() default {0,6};
    /**
     * 	需要脱敏的字符长度
     * 	规则 输入 3 :则根据index下标索引对应脱敏3个字符 默认6个长度脱敏
     **/
    int size() default 6;
    class ConvertDesensitization extends StdSerializer<Object> implements ContextualSerializer {
        private int[] index;
        private int size;
        public ConvertDesensitization() {
            super(Object.class);
        }
        private ConvertDesensitization(int[] index,int size) {
            super(Object.class);
            this.size = size;
            this.index = index;
        }
        @Override
        public void serialize(Object value, JsonGenerator jgen,
                              SerializerProvider provider) throws IOException {
            char[] str = value.toString().toCharArray();
            StringBuilder builder = new StringBuilder();
            String char1 = (String) value;
            if(str.length > 0) {
                //字符长度超长处理
                if(index[0] < str.length && index[1] < str.length) {
                    //使用默认初始值的脱敏处理
                    if(index[0] == 0) {
                        //如果输入脱敏大小长度小于0或大于原始脱敏字符长度,则全脱敏字符
                        if (size < 0 || size < str.length) {
                            char[] charStr = char1.substring(index[1], str.length).toCharArray();
                            char[] charStr1 = char1.substring(index[0], index[1]).toCharArray();
                            builder.append(charStr1);
                            for (int i = 0; i < charStr.length; i++) {
                                if(size > i) {
                                    builder.append("*");
                                }else {
                                    builder.append(charStr[i]);
                                }
                            }
                        }else {
                            builder.append(getDefaultChar((String) value,"left"));
                        }
                    }else {
                        //从中间位置截取脱敏处理
                        //如果输入脱敏大小长度小于0或大于原始脱敏字符长度,则全脱敏字符
                        if (size < 0 || size < str.length) {
                            char[] charStr = char1.substring(index[0], str.length - index[1] + 1).toCharArray(); //2 6-4 2 //中间截取部分
                            List<Integer> prefix = getPrefix(index[0], (String) value);
                            //List<Integer> suffix = getSuffix(index[0],index[1], (String) value);
                            for (Integer integer : prefix) {
                                builder.append(str[integer]);
                            }
                            for (int i = 0; i < charStr.length; i++) {
                                if (size > i) {
                                    builder.append("*");
                                } else {
                                    builder.append(charStr[i]);
                                }
                            }
                            char[] chars = Arrays.copyOfRange(str, index[1], str.length);
                            builder.append(String.valueOf(chars));
                        }else {
                            builder.append(getDefaultChar((String) value,"right"));
                        }
                    }
                }else {
                    //默认处理
                    builder.append(getDefaultChar((String) value,""));
                }
            }
            jgen.writeString(builder.toString());
        }
        /**
         * 默认的填充方式
         * @param str 原始字符串
         * @param position 位置
         * @return
         */
        String getDefaultChar(String str,String position){
            char[] desensitizationStr = str.toCharArray();
            for(int i=0;i<desensitizationStr.length;i++){
                if("left".equals(position)){
                    if(i != 0){
                        desensitizationStr[i] = '*';
                    }
                }else if("right".equals(position)){
                    if(i != desensitizationStr.length-1){
                        desensitizationStr[i] = '*';
                    }
                }else {
                    if(i != 0 && i != desensitizationStr.length-1){
                        desensitizationStr[i] = '*';
                    }
                }
            }
            return String.valueOf(desensitizationStr);
        }
        /**
         * 获取字符前缀下标
         * @param index 下标
         * @param val 原始字符串
         * @return
         */
        List<Integer> getPrefix(int index,String val){
            //int[] chars = {};
            List<Integer> listIndex = new ArrayList<>();
            for(int i=0;i<val.length();i++){
                if(i != index){ //0 1 != 2
                    listIndex.add(i);
                    continue;
                }
                break;
            }
            return listIndex;
        }
        @Override
        public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
            int[] index = {0,6}; //初始值
            int size = 6; //初始值
            Desensitization ann = null;
            if (property != null) {
                ann = property.getAnnotation(Desensitization.class);
            }
            if (ann != null) {
                index = ann.index();
                size = ann.size();
            }
            return new Desensitization.ConvertDesensitization(index,size);
        }
    }
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • springboot @Async 注解如何实现方法异步

    目录 @Async注解如何实现方法异步 一.springboot的App类需要的注解 二.service层的注解 三.调用层 异步注解@Async的使用以及注意事项 第一步开启异步 下面显示配置线程的代码实现 使用@Async导致异步不成功的情况 @Async注解如何实现方法异步 处理大批量数据的时候,效率很慢.所以考虑一下使用多线程. 刚开始自己手写的一套,用了线程池启动固定的线程数进行跑批.但是后来老大考虑到自己手写的风险不好控制,所以使用spring的方法. 这里没有详细介绍,只有简单的d

  • SpringBoot常用注解详细整理

    目录 前言 一.@SpringBootApplication 二.@Bean 三.@Autowired 四.Component家族 五.@RestController 六.@Scope 七.@Configuration 八.@RequsetMapping 八.@GetMapping 九.@Configuration 十.@PostMapping 十一.@PutMapping 十二.@DeleteMapping 十三.@ParhVariable和@RequestParam 十四.@RequestB

  • SpringBoot使用@PostConstruct注解导入配置方式

    目录 使用@PostConstruct注解导入配置 使用@PostConstruct注解,完成静态对象注入 为什么static对象不可直接使用@Autowired注入? @PostConstruct和@PreDestroy 使用@PostConstruct注解导入配置 通过@PostConstruct注解能够通过一种更友好的方式将配置进行导入 代码如下: /** * 引导类 * * @author zhangzhixiang * @date 2018/09/18 14:51:39 */ @Con

  • SpringBoot @Autowired注解注入规则介绍

    目录 @Autowired注解注入规则 验证 小结一下 @Autowired注解无法自动注入的错误 @Autowired注解注入规则 @Autowired - 注入默认根据类型,匹配不到则根据bean名字 Spring中注解方式的默认beanName生成规则: 在Spring中,当我们配置一个bean的时候,可以不指定name,这样的话,Spring会生成一个默认的beanName 1. 驼峰形式类名首字母小写:UserService--userService 2. 特殊情况--当类名的首字母和

  • Springboot常用注解及配置文件加载顺序详解

    Springboot常用注解及底层实现 1.@SpringBootApplication:这个注解标识了一个SpringBoot工程,她实际上是另外三个注解的组合,分别是: @SpringBootConfiguration:源码可以看到,这个注解除了元注解外,实际就只有一个@Configuration,把该类变成一个配置类,表示启动类也是一个配置类: @EnableAutoConfiguration:是开启自动配置的功能,向Spring容器中导入了一个Selector,用来加载ClassPath

  • @SpringBootTest 注解报红问题及解决

    目录 打注解@SpringBootTest的时候不会出现提示 SpringBoot模块中启动类的注解标红 打注解@SpringBootTest的时候不会出现提示 但是又导入了 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> 这个开发场景,于是我想

  • SpringBoot自定义Redis实现缓存序列化详解

    目录 1.自定义RedisTemplate 1.1.Redis API默认序列化机制 1.2.自定义RedisTemplate序列化机制 1.3.效果测试 2.自定义RedisCacheManager 2.1.Redis注解默认序列化机制 2.2.自定义RedisCacheManager 刚刚完成了Spring Boot整合Redis进行了数据的缓存管理,但缓存管理的实体类数据使用的是JDK序列化方式,不便于使用可视化管理工具进行查看和管理. 接下来分别针对基于注解的Redis缓存实现和基于AP

  • SpringBoot自定义路由覆盖实现流程详解

    目录 背景 设计 实现 注解定义 注解扫描及管理 自定义RequestMappingHandlerMapping 注册RequestMappingHandlerMapping 使用示例 背景 公司最近有一个项目二期需要对一些功能进行改造,涉及部分框架内置业务接口个性化定制,兼容老接口功能并且增加一部分新的数据返回,由于前端调用这些接口分布较多且较为零碎,修改测试成本较大,所以打算在框架层面提供路由覆盖功能,加快项目进度减少无技术含量的修改带来的系统风险 设计 提供自定义注解指定需要覆盖的路由及新

  • SpringBoot 自定义注解之脱敏注解详解

    目录 自定义注解之脱敏注解 一.脱敏后的效果 二.代码 1.脱敏注解 2.定义脱敏类型 3.敏感工具类 4.脱敏序列化信息 小结一下 自己手写的一个高效自定义字符串脱敏注解 自己写了个 仅供参考 自定义注解之脱敏注解 数据脱敏是指对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护.需求是把返回到前端的数据进行脱敏,以免造成隐私信息的泄露. 一.脱敏后的效果 这样显示很不好吧,所有信息都泄露了 这样就很好了吧 二.代码 1.脱敏注解 @Retention(RetentionPol

  • SpringBoot中注解@AliasFor的使用详解

    目录 简介 用法1:注解的属性互为别名 简介 实例 用法2.继承父注解的属性,不重写属性名 简介 代码 用法3:继承父注解的属性,并重写属性名 简介 代码 简介 本文用示例介绍@AliasFor(别名)注解的用法. 用法1:注解的属性互为别名 简介 它可以注解到自定义注解的两个属性上,表示这两个互为别名,也就是说这两个属性其实同一个含义. 其中一个属性名必须是"value" 无论指明设置哪个属性名设置属性值,另一个属性名也是同样属性值,也可以缺省属性名. 若两个都指明属性值,要求值必须

  • SpringBoot注入自定义的配置文件的方法详解

    目录 一.简介 二.代码实践 2.1 通过@value注解实现参数加载 2.2 通过@ConfigurationProperties注解实现参数加载 2.3 通过@PropertySource注解实现配置文件加载 2.4 通过自定义环境处理类,实现配置文件的加载 2.5 最后,我们来介绍一下yml文件读取 一.简介 在实际的项目开发过程中,我们经常需要将某些变量从代码里面抽离出来,放在配置文件里面,以便更加统一.灵活的管理服务配置信息.比如,数据库.eureka.zookeeper.redis.

  • 关于fastjson的@JSONField注解的一些问题(详解)

    @JSONField 看源码它可以作用于字段和方法上. 引用网上说的, 一.作用Field @JSONField作用在Field时,其name不仅定义了输入key的名称,同时也定义了输出的名称. 但是我在使用中,发现并不如上所说. 例如 @JSONField(name="project_id") private Long ProjectID 发现bean 转json的时候并是"project_id":xxx的形式,json转bean的时候也不会把"proj

  • 基于注解的组件扫描详解

    在使用组件扫描时,需要现在XML配置中指定扫描的路径 <context:component-scan back-package="yangjq.test"> 容器实例化会扫描yangjq.test包及其子包下的所有组件类. 只有当组件类定义前面有下面的注解标记时,这些组件类才会被扫描到Spring容器 - @Component 通用注解 - @Name 通用注解 - @Repository 持久化层组件注解 - @Service 业务层组件注解 - @Controller

  • Spring中@Async注解实现异步调详解

    异步调用 在解释异步调用之前,我们先来看同步调用的定义:同步就是整个处理过程顺序执行,当各个过程都执行完毕,并返回结果. 异步调用则是只是发送了调用的指令,调用者无需等待被调用的方法完全执行完毕,继续执行下面的流程.例如, 在某个调用中,需要顺序调用 A, B, C三个过程方法:如他们都是同步调用,则需要将他们都顺序执行完毕之后,过程才执行完毕: 如B为一个异步的调用方法,则在执行完A之后,调用B,并不等待B完成,而是执行开始调用C,待C执行完毕之后,就意味着这个过程执行完毕了. 概述说明 Sp

  • SpringBoot2底层注解@Configuration配置类详解

    目录 SpringBoot2底层注解@Configuration配置类 一.配置类 二.配置类本身也是组件 三.proxyBeanMethods 属性 有组件依赖的场景 SpringBoot2底层注解@Configuration配置类 一.配置类 @Configuration这个注解作用就是告诉 springboot 这是一个配置类. 这个配置已经不陌生了,在之前 spring 相关的使用全注解方式时,就使用到了配置类. 在配置类里,可以使用@Bean标记在方法上,给容器注册组件,默认也是单实例

  • Java通过反射注解赋值的方法详解

    目录 问题描述 最终解决 if/else 普通解法 通过反射注解赋值属性 解题思路 汇总某些字段的和 总结 源码 前段时间,领导分配一个统计销售区域汇总的数据,解决方案使用到了反射获取注解,通过注解获取属性或者设置字段属性. 问题描述 查询公司列表,分别是公司id.区域id.区域名称: 公司id 区域id 区域名称 1 1 华南 2 2 华北 3 2 华北 4 3 华东 5 3 华东 创建公司类Company: public class Company { public Company(Inte

随机推荐