自定义序列化器+自定义脱敏器,实现数据脱敏

1. 为何要对数据进行脱敏

对数据进行脱敏(Data Masking)主要是出于以下几个原因:

  1. 保护隐私

    • 数据脱敏可以有效地隐藏敏感信息,如个人身份信息、银行账户信息等,从而防止这些信息在开发、测试或数据分析过程中被泄露。这对于遵守数据保护法规至关重要,比如GDPR和中国的《个人信息保护法》。
  2. 安全合规

    • 许多行业都有严格的数据保护标准和合规要求。通过对数据进行脱敏处理,企业能够确保其数据处理活动符合这些规定,避免因数据泄露导致的法律风险和罚款。
  3. 降低风险

    • 在软件开发过程中,使用真实数据可能会增加数据泄露的风险。通过使用脱敏数据,即使发生数据泄露,也只会影响非敏感信息,从而降低了潜在的损害。

废话不多说,直接上代码

2. 自定义字符串序列化器

原理:通过序列化器拦截JSON字符串的序列化,在序列化前将字符进行替换

1. 自定义注解 -> 指定序列化器

@Documented
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside // 此注解是其他所有 jackson 注解的元注解,打上了此注解的注解表明是 jackson 注解的一部分
@JsonSerialize(using = StringDesensitizeSerializer.class) // 指定序列化器
public @interface DesensitizeBy {

    /**
     * 脱敏处理器
     */
    @SuppressWarnings("rawtypes")
    Class<? extends DesensitizationHandler> handler();

}

2. 实现自定义序列化器

public class StringDesensitizeSerializer extends StdSerializer<String> implements ContextualSerializer {

    @Getter
    @Setter
    private DesensitizationHandler desensitizationHandler;


    protected StringDesensitizeSerializer() {
        super(String.class);
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        // 查看bean的属性上有没有DesensitizeBy注解
        DesensitizeBy annotation = beanProperty.getAnnotation(DesensitizeBy.class);
        if (Objects.isNull(annotation)) {
            return this;
        }
        // 创建一个 StringDesensitizeSerializer 对象,使用 DesensitizeBy 对应的处理器
        StringDesensitizeSerializer serializer = new StringDesensitizeSerializer();
        serializer.setDesensitizationHandler(Singleton.get(annotation.handler()));
        return serializer;
    }

    @Override
    public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        if (StrUtil.isBlank(value)) {
            jsonGenerator.writeNull();
            return;
        }
        //需要脱敏的属性名
        String currentName = jsonGenerator.getOutputContext().getCurrentName();
        //类的所有属性名极其对应的值
        Object currentValue = jsonGenerator.getCurrentValue();
        Class<?> currentValueClass = currentValue.getClass();
        //通过反射拿到指定属性名的值
        Field field = ReflectUtil.getField(currentValueClass, currentName);

        for (Annotation annotation : field.getAnnotations()) {
            //如果该类属性上面有DesensitizeBy,则进行序列化
            if (AnnotationUtil.hasAnnotation(annotation.annotationType(), DesensitizeBy.class)) {
                value = this.desensitizationHandler.desensitize(value, annotation);
                jsonGenerator.writeString(value);
                return;
            }
        }
    }
}

3. 动态实现不同的脱敏器

1. 顶层脱敏接口

public interface DesensitizationHandler<T extends Annotation> {

    /**
     * 脱敏
     *
     * @param origin     原始字符串
     * @param annotation 注解信息
     * @return 脱敏后的字符串
     */
    String desensitize(String origin, T annotation);

    /**
     * 是否禁用脱敏的 Spring EL 表达式
     * <p>
     * 如果返回 true 则跳过脱敏
     *
     * @param annotation 注解信息
     * @return 是否禁用脱敏的 Spring EL 表达式
     */
    default String getDisable(T annotation) {
        // 约定:默认就是 enable() 属性。如果不符合,子类重写
        try {
            return (String) ReflectUtil.invoke(annotation, "disable");
        } catch (Exception ex) {
            return "";
        }
    }

}

2. 脱敏器默认实现类

public abstract class AbstractSliderDesensitizationHandler<T extends Annotation> implements DesensitizationHandler<T> {
    @Override
    public String desensitize(String origin, T annotation) {
        Object disable = SpringExpressionUtils.parseExpression(getDisable(annotation));
        if (Boolean.FALSE.equals(disable)) {
            return origin;
        }

        // 2. 执行脱敏
        int prefixKeep = getPrefixKeep(annotation);
        int suffixKeep = getSuffixKeep(annotation);
        String replacer = getReplacer(annotation);

        int length = origin.length();

        // 情况一:原始字符串长度小于等于保留长度,则原始字符串全部替换
        if (prefixKeep >= length || suffixKeep >= length) {
            return buildReplacerByLength(replacer, length);
        }

        // 情况二:原始字符串长度小于等于前后缀保留字符串长度,则原始字符串全部替换
        if ((prefixKeep + suffixKeep) >= length) {
            return buildReplacerByLength(replacer, length);
        }

        // 情况三:原始字符串长度大于前后缀保留字符串长度,则替换中间字符串
        int interval = length - prefixKeep - suffixKeep;
        return origin.substring(0, prefixKeep) +
                buildReplacerByLength(replacer, interval) +
                origin.substring(prefixKeep + interval);
    }


    /**
     * 根据长度循环构建替换符
     *
     * @param replacer 替换符
     * @param length   长度
     * @return 构建后的替换符
     */
    private String buildReplacerByLength(String replacer, int length) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < length; i++) {
            builder.append(replacer);
        }
        return builder.toString();
    }


    /**
     * 前缀保留长度
     *
     * @param annotation 注解信息
     * @return 前缀保留长度
     */
    abstract Integer getPrefixKeep(T annotation);

    /**
     * 后缀保留长度
     *
     * @param annotation 注解信息
     * @return 后缀保留长度
     */
    abstract Integer getSuffixKeep(T annotation);

    /**
     * 替换符
     *
     * @param annotation 注解信息
     * @return 替换符
     */
    abstract String getReplacer(T annotation);
}

3. 身份证脱敏实现类

public class IdCardDesensitization extends AbstractSliderDesensitizationHandler<IdCardDesensitize> {


    @Override
    Integer getPrefixKeep(IdCardDesensitize annotation) {
        return annotation.prefixKeep();
    }

    @Override
    Integer getSuffixKeep(IdCardDesensitize annotation) {
        return annotation.suffixKeep();
    }

    @Override
    String getReplacer(IdCardDesensitize annotation) {
        return annotation.replacer();
    }
}

4. 自定义脱敏注解,可配置脱敏格式

@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@DesensitizeBy(handler = IdCardDesensitization.class)
public @interface IdCardDesensitize {

    /**
     * 前缀保留长度
     */
    int prefixKeep() default 6;

    /**
     * 后缀保留长度
     */
    int suffixKeep() default 2;

    /**
     * 替换规则,身份证号码;比如:530321199204074611 脱敏之后为 530321**********11
     */
    String replacer() default "*";

    /**
     * 是否禁用脱敏
     *
     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
     */
    String disable() default "";

}

4. 测试

@Data
@Builder
public class User {
    
    private Integer id;//主键ID
    
    private String username;//用户名
    
    @IdCardDesensitize
    private String idCardNo; //身份证号
}
{
    "id": 2,
    "username": "young",
    "nickname": "",
    "idCardNo": "510922**********72",
  }

5. 需要用到的工具类

   <dependency>
      <groupId>cn.hutool</groupId>
      <artifactId>hutool-all</artifactId>
      <version>5.8.32</version>
    </dependency>

    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
    </dependency>
public class SpringExpressionUtils {

    /**
     * Spring EL 表达式解析器
     */
    private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
    /**
     * 参数名发现器
     */
    private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();

    private SpringExpressionUtils() {
    }

    /**
     * 从切面中,单个解析 EL 表达式的结果
     *
     * @param joinPoint        切面点
     * @param expressionString EL 表达式数组
     * @return 执行界面
     */
    public static Object parseExpression(JoinPoint joinPoint, String expressionString) {
        Map<String, Object> result = parseExpressions(joinPoint, Collections.singletonList(expressionString));
        return result.get(expressionString);
    }

    /**
     * 从切面中,批量解析 EL 表达式的结果
     *
     * @param joinPoint         切面点
     * @param expressionStrings EL 表达式数组
     * @return 结果,key 为表达式,value 为对应值
     */
    public static Map<String, Object> parseExpressions(JoinPoint joinPoint, List<String> expressionStrings) {
        // 如果为空,则不进行解析
        if (CollUtil.isEmpty(expressionStrings)) {
            return MapUtil.newHashMap();
        }

        // 第一步,构建解析的上下文 EvaluationContext
        // 通过 joinPoint 获取被注解方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        // 使用 spring 的 ParameterNameDiscoverer 获取方法形参名数组
        String[] paramNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method);
        // Spring 的表达式上下文对象
        EvaluationContext context = new StandardEvaluationContext();
        // 给上下文赋值
        if (ArrayUtil.isNotEmpty(paramNames)) {
            Object[] args = joinPoint.getArgs();
            for (int i = 0; i < paramNames.length; i++) {
                context.setVariable(paramNames[i], args[i]);
            }
        }

        // 第二步,逐个参数解析
        Map<String, Object> result = MapUtil.newHashMap(expressionStrings.size(), true);
        expressionStrings.forEach(key -> {
            Object value = EXPRESSION_PARSER.parseExpression(key).getValue(context);
            result.put(key, value);
        });
        return result;
    }

    /**
     * 从 Bean 工厂,解析 EL 表达式的结果
     *
     * @param expressionString EL 表达式
     * @return 执行界面
     */
    public static Object parseExpression(String expressionString) {
        if (StrUtil.isBlank(expressionString)) {
            return null;
        }
        Expression expression = EXPRESSION_PARSER.parseExpression(expressionString);
        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setBeanResolver(new BeanFactoryResolver(SpringUtil.getApplicationContext()));
        return expression.getValue(context);
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值