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

目录

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

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

      一、脱敏后的效果
      SpringBoot|SpringBoot 自定义注解之脱敏注解详解
      文章图片

      这样显示很不好吧,所有信息都泄露了
      SpringBoot|SpringBoot 自定义注解之脱敏注解详解
      文章图片

      这样就很好了吧
      【SpringBoot|SpringBoot 自定义注解之脱敏注解详解】
      二、代码

      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@AllArgsConstructorpublic class SensitiveSerialize extends JsonSerializer implements ContextualSerializer {/*** 脱敏类型*/private SensitiveTypeEnum sensitiveTypeEnum; /*** 前几位不脱敏*/private Integer prefixNoMaskLen; /*** 最后几位不脱敏*/private Integer suffixNoMaskLen; /*** 用什么打码*/private String symbol; @Overridepublic 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); }}@Overridepublic 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 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; }@Overridepublic 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 prefix = getPrefix(index[0], (String) value); //List 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 getPrefix(int index,String val){//int[] chars = {}; List listIndex = new ArrayList<>(); for(int i=0; i 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); }}}
      以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

        推荐阅读