作者 RuoYi

新增数据脱敏过滤注解

  1 +package com.ruoyi.common.annotation;
  2 +
  3 +import java.lang.annotation.ElementType;
  4 +import java.lang.annotation.Retention;
  5 +import java.lang.annotation.RetentionPolicy;
  6 +import java.lang.annotation.Target;
  7 +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
  8 +import com.fasterxml.jackson.databind.annotation.JsonSerialize;
  9 +import com.ruoyi.common.config.serializer.SensitiveJsonSerializer;
  10 +import com.ruoyi.common.enums.DesensitizedType;
  11 +
  12 +/**
  13 + * 数据脱敏注解
  14 + *
  15 + * @author ruoyi
  16 + */
  17 +@Retention(RetentionPolicy.RUNTIME)
  18 +@Target(ElementType.FIELD)
  19 +@JacksonAnnotationsInside
  20 +@JsonSerialize(using = SensitiveJsonSerializer.class)
  21 +public @interface Sensitive
  22 +{
  23 + DesensitizedType desensitizedType();
  24 +}
  1 +package com.ruoyi.common.config.serializer;
  2 +
  3 +import java.io.IOException;
  4 +import java.util.Objects;
  5 +import com.fasterxml.jackson.core.JsonGenerator;
  6 +import com.fasterxml.jackson.databind.BeanProperty;
  7 +import com.fasterxml.jackson.databind.JsonMappingException;
  8 +import com.fasterxml.jackson.databind.JsonSerializer;
  9 +import com.fasterxml.jackson.databind.SerializerProvider;
  10 +import com.fasterxml.jackson.databind.ser.ContextualSerializer;
  11 +import com.ruoyi.common.annotation.Sensitive;
  12 +import com.ruoyi.common.core.domain.model.LoginUser;
  13 +import com.ruoyi.common.enums.DesensitizedType;
  14 +import com.ruoyi.common.utils.SecurityUtils;
  15 +
  16 +/**
  17 + * 数据脱敏序列化过滤
  18 + *
  19 + * @author ruoyi
  20 + */
  21 +public class SensitiveJsonSerializer extends JsonSerializer<String> implements ContextualSerializer
  22 +{
  23 + private DesensitizedType desensitizedType;
  24 +
  25 + @Override
  26 + public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException
  27 + {
  28 + if (desensitization())
  29 + {
  30 + gen.writeString(desensitizedType.desensitizer().apply(value));
  31 + }
  32 + else
  33 + {
  34 + gen.writeString(value);
  35 + }
  36 + }
  37 +
  38 + @Override
  39 + public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
  40 + throws JsonMappingException
  41 + {
  42 + Sensitive annotation = property.getAnnotation(Sensitive.class);
  43 + if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass()))
  44 + {
  45 + this.desensitizedType = annotation.desensitizedType();
  46 + return this;
  47 + }
  48 + return prov.findValueSerializer(property.getType(), property);
  49 + }
  50 +
  51 + /**
  52 + * 是否需要脱敏处理
  53 + */
  54 + private boolean desensitization()
  55 + {
  56 + try
  57 + {
  58 + LoginUser securityUser = SecurityUtils.getLoginUser();
  59 + // 管理员不脱敏
  60 + return !securityUser.getUser().isAdmin();
  61 + }
  62 + catch (Exception e)
  63 + {
  64 + return true;
  65 + }
  66 + }
  67 +}
  1 +package com.ruoyi.common.enums;
  2 +
  3 +import java.util.function.Function;
  4 +import com.ruoyi.common.utils.DesensitizedUtil;
  5 +
  6 +/**
  7 + * 脱敏类型
  8 + *
  9 + * @author ruoyi
  10 + */
  11 +public enum DesensitizedType
  12 +{
  13 + /**
  14 + * 姓名,第2位星号替换
  15 + */
  16 + USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")),
  17 +
  18 + /**
  19 + * 密码,全部字符都用*代替
  20 + */
  21 + PASSWORD(DesensitizedUtil::password),
  22 +
  23 + /**
  24 + * 身份证,中间10位星号替换
  25 + */
  26 + ID_CARD(s -> s.replaceAll("(\\d{4})\\d{10}(\\d{4})", "$1** **** ****$2")),
  27 +
  28 + /**
  29 + * 手机号,中间4位星号替换
  30 + */
  31 + PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),
  32 +
  33 + /**
  34 + * 电子邮箱,仅显示第一个字母和@后面的地址显示,其他星号替换
  35 + */
  36 + EMAIL(s -> s.replaceAll("(^.)[^@]*(@.*$)", "$1****$2")),
  37 +
  38 + /**
  39 + * 银行卡号,保留最后4位,其他星号替换
  40 + */
  41 + BANK_CARD(s -> s.replaceAll("\\d{15}(\\d{3})", "**** **** **** **** $1")),
  42 +
  43 + /**
  44 + * 车牌号码,包含普通车辆、新能源车辆
  45 + */
  46 + CAR_LICENSE(DesensitizedUtil::carLicense);
  47 +
  48 + private final Function<String, String> desensitizer;
  49 +
  50 + DesensitizedType(Function<String, String> desensitizer)
  51 + {
  52 + this.desensitizer = desensitizer;
  53 + }
  54 +
  55 + public Function<String, String> desensitizer()
  56 + {
  57 + return desensitizer;
  58 + }
  59 +}
  1 +package com.ruoyi.common.utils;
  2 +
  3 +/**
  4 + * 脱敏工具类
  5 + *
  6 + * @author ruoyi
  7 + */
  8 +public class DesensitizedUtil
  9 +{
  10 + /**
  11 + * 密码的全部字符都用*代替,比如:******
  12 + *
  13 + * @param password 密码
  14 + * @return 脱敏后的密码
  15 + */
  16 + public static String password(String password)
  17 + {
  18 + if (StringUtils.isBlank(password))
  19 + {
  20 + return StringUtils.EMPTY;
  21 + }
  22 + return StringUtils.repeat('*', password.length());
  23 + }
  24 +
  25 + /**
  26 + * 车牌中间用*代替,如果是错误的车牌,不处理
  27 + *
  28 + * @param carLicense 完整的车牌号
  29 + * @return 脱敏后的车牌
  30 + */
  31 + public static String carLicense(String carLicense)
  32 + {
  33 + if (StringUtils.isBlank(carLicense))
  34 + {
  35 + return StringUtils.EMPTY;
  36 + }
  37 + // 普通车牌
  38 + if (carLicense.length() == 7)
  39 + {
  40 + carLicense = StringUtils.hide(carLicense, 3, 6);
  41 + }
  42 + else if (carLicense.length() == 8)
  43 + {
  44 + // 新能源车牌
  45 + carLicense = StringUtils.hide(carLicense, 3, 7);
  46 + }
  47 + return carLicense;
  48 + }
  49 +}
@@ -23,6 +23,9 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils @@ -23,6 +23,9 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
23 /** 下划线 */ 23 /** 下划线 */
24 private static final char SEPARATOR = '_'; 24 private static final char SEPARATOR = '_';
25 25
  26 + /** 星号 */
  27 + private static final char ASTERISK = '*';
  28 +
26 /** 29 /**
27 * 获取参数不为空值 30 * 获取参数不为空值
28 * 31 *
@@ -164,6 +167,49 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils @@ -164,6 +167,49 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
164 } 167 }
165 168
166 /** 169 /**
  170 + * 替换指定字符串的指定区间内字符为"*"
  171 + *
  172 + * @param str 字符串
  173 + * @param startInclude 开始位置(包含)
  174 + * @param endExclude 结束位置(不包含)
  175 + * @return 替换后的字符串
  176 + */
  177 + public static String hide(CharSequence str, int startInclude, int endExclude)
  178 + {
  179 + if (isEmpty(str))
  180 + {
  181 + return NULLSTR;
  182 + }
  183 + final int strLength = str.length();
  184 + if (startInclude > strLength)
  185 + {
  186 + return NULLSTR;
  187 + }
  188 + if (endExclude > strLength)
  189 + {
  190 + endExclude = strLength;
  191 + }
  192 + if (startInclude > endExclude)
  193 + {
  194 + // 如果起始位置大于结束位置,不替换
  195 + return NULLSTR;
  196 + }
  197 + final char[] chars = new char[strLength];
  198 + for (int i = 0; i < strLength; i++)
  199 + {
  200 + if (i >= startInclude && i < endExclude)
  201 + {
  202 + chars[i] = ASTERISK;
  203 + }
  204 + else
  205 + {
  206 + chars[i] = str.charAt(i);
  207 + }
  208 + }
  209 + return new String(chars);
  210 + }
  211 +
  212 + /**
167 * 截取字符串 213 * 截取字符串
168 * 214 *
169 * @param str 字符串 215 * @param str 字符串