信息发布→ 登录 注册 退出

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

发布时间:2026-01-11

点击量:
目录
  • 自定义注解之脱敏注解
    • 一、脱敏后的效果
    • 二、代码
      • 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: 2025/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);
              }
          }
      }
      

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

      在线客服
      服务热线

      服务热线

      4008888355

      微信咨询
      二维码
      返回顶部
      ×二维码

      截屏,微信识别二维码

      打开微信

      微信号已复制,请打开微信添加咨询详情!