开发中对敏感字段进行脱敏方法

注解实现数据脱敏操作

在项目中经常使用到数据脱敏,这里使用 fasterxml.jackson来实现返回数据的脱敏操作

在这里借助 com.fasterxml.jackson.databind.JsonSerializer接口来完成对数据序列化的时候的操作

*引入包文件*

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.11.4</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.11.4</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.11.4</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.13.0</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.76</version>
</dependency>

一、创建策略

在序列化的时候需要指定序列化的规则,例如

字段长度 大于 10 前三后四明文字段长度 小于 4 无明文字段长度 大其他 后四明文

这里使用工具类来实现这个通用的操作

public class SensitiveUtils {
    public static final String SYMBOL_STAR = "*";
    /**
     * 通用脱敏
     * 字段长度 大于 10 前三后四明文
     * 字段长度 小于 4 无明文
     * 字段长度 大其他 后四明文
     *
     * @param sensitiveValue 敏感值
     * @return 脱敏值
     */
    public static String commonSensitive(String sensitiveValue) {
        if (StringUtils.isBlank(sensitiveValue)) {
            return sensitiveValue;
        }
        if (sensitiveValue.length() > 10) {
            int s = sensitiveValue.length() - 7;
            return sensitiveValue.replaceAll("(\\w{3})\\w{" + s + "}(\\w{4})", "$1" + getStars(s) + "$2");
        } else if (sensitiveValue.length() < 4) {
            return getStars(sensitiveValue.length());
        } else {
            int s = sensitiveValue.length() - 4;
            return sensitiveValue.replaceAll("\\w{" + s + "}(\\w{4})", getStars(s) + "$1");
        }
    }
    /**
     * 判断是否是脱敏值
     * 是脱敏值 返回 true
     * 不是脱敏值 返回 false
     *
     * @param value 输入值
     * @return 返回结果
     */
    public static boolean simpleInvalidSensitive(String value) {
        if (StringUtils.isBlank(value)) {
            return false;
        }
        return value.contains(SYMBOL_STAR);
    }
    public static String getStars(int s) {
        String stars = StringUtils.EMPTY;
        for (int i = 0; i < s; i++) {
            stars += SYMBOL_STAR;
        }
        return stars;
    }
}

上面的是一个通用的工具类,下面,我们使用接口来实例化这个通用的策略:

首先是接口:


public interface MyStrategy {   
String desensitizationByPattern(String source);
}

//然后构建一个默认的策略:

public class MyDefaultStrategy implements MyStrategy {
    public String desensitizationByPattern(String source) {
        return SensitiveUtils.commonSensitive(source);
    }
}

这个操作就是传入一个字段返回脱敏后的字符串,到这里就完成了一个策略的制定

二、构建序列化注解

这个注解用来明确字段序列化时需要的序列化器和使用的策略,这里要用到注解 @JsonSerialize,它的参数 using用来明确使用的序列化器,这里使用自定义的序列化器,下一节介绍序列化器的创建。


@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = MySensitiveInfoSerialize.class)
@Inherited
public @interface CommonSensitive {
    /**
     * 脱敏策略
     *
     * @return
     */
    Class<? extends MyStrategy> strategy() default MyDefaultStrategy.class;
}

从上面可以看出来,这里的参数是一个接口性质的序列化策略,也就是说,后面是支持自定义的。

三、创建序列化器

序列化器要实现接口 com.fasterxml.jackson.databind.JsonSerializer

并实现方法


@Slf4j
@NoArgsConstructor
public class MySensitiveInfoSerialize extends JsonSerializer<String> implements
        ContextualSerializer {
    private MyStrategy myStrategy;
    public MySensitiveInfoSerialize(MyStrategy myStrategy) {
        this.myStrategy = myStrategy;
    }
    @Override
    public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString(myStrategy.desensitizationByPattern(s));
    }
    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        if (beanProperty != null) {
            // 非 String 类直接跳过
            if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
                CommonSensitive sensitiveInfo = beanProperty.getAnnotation(CommonSensitive.class);
                if (sensitiveInfo == null) {
                    sensitiveInfo = beanProperty.getContextAnnotation(CommonSensitive.class);
                }
                if (sensitiveInfo != null) {
                    Class<? extends MyStrategy> clazz = sensitiveInfo.strategy();
                    // 如果能得到注解,就将注解的 value 传入 SensitiveInfoSerialize
                    try {
                        return new MySensitiveInfoSerialize(clazz.getDeclaredConstructor().newInstance());
                    } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
                             NoSuchMethodException e) {
                        throw new RuntimeException(e);
                    }
                }
                return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
            }
        }
        return serializerProvider.findNullValueSerializer(null);
    }
}

serialize使用序列化器序列化字段

createContextual创建序列化上下文,主要是明确使用的序列化策略或者其他方式

到这里基本就可以使用了

四、自定义序列化策略

从上面我们的策略是用接口的方式使用的,这里要实现这个接口


public class MyCustomizeNameStrategy implements MyStrategy {
    @Override
    public String desensitizationByPattern(String source) {
        if (StringUtils.isNotBlank(source)) {
            int s = source.length();
            int ss = source.lastIndexOf(".");
            if (ss == -1) {
                ss = 1;
                int sss = s - ss;
                return source.replaceAll("(\\S{" + ss + "})\\S{" + sss + "}", "$1" + SensitiveUtils.getStars(sss));
            } else {
                int sss = s - ss;
                return source.replaceAll("\\S{" + ss + "}(\\S{" + sss + "})", SensitiveUtils.getStars(ss) + "$1");
            }
        } else {
            return source;
        }
    }
}

这个是用来序列化姓名的,保留姓,这样就完成了,在使用注解 @CommonSensitive就可以使用了

五、规范化注解使用

在项目中经常要对不同性质的字段脱敏,例如邮箱,电话,身份证号码等,这些策略都不同,我们可以使用不同的序列化策略,但是这样使用不好的地方是我们要记住使用策略,为了方便使用,我们可以通过注解直接表明不同的注解表示不同的脱敏方式,无参的方式使用更加方便,这里选择使用邮件脱敏来说明

首先自定义一个邮件脱敏的规则:


public class MyEmailStrategy implements MyStrategy {
    @Override
    public String desensitizationByPattern(String source) {
        if (StringUtils.isNotBlank(source)) {
            int s = source.length();
            int ss = source.indexOf("@");
            int sss = s - ss;
            return source.replaceAll("\\S{" + ss + "}(\\S{" + sss + "})", SensitiveUtils.getStars(ss)+"$1");
        } else {
            return source;
        }
    }
}

然后通过使用我们定义的原始注解包装一下

@Documented@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@CommonSensitive(strategy = MyEmailStrategy.class)@JacksonAnnotationsInsidepublic @interface MyEmailSensitive {}

这样就完成了对字段属性的序列化注解,使用的策略正是上面我们定义的,这个注解还是无参的。

六、使用案例

先建立一个序列化对象

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@CommonSensitive(strategy = MyEmailStrategy.class)
@JacksonAnnotationsInside
public @interface MyEmailSensitive {
}
//上面我们使用不同的使用方式脱敏

//创建一个请求接口来实现脱敏操作

@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {
    @PostMapping("/submit2")
    public Employee submit2(@RequestBody Employee employee) {
        log.info(JSON.toJSONString(employee));
        return employee;
    }
}

测试使用报文:

{    
  "name": "王斌强",
 "phone": "18637986236",    
 "email": "y.leffh@qymwmaej.yu"
}

打印日志:

{"email":"y.leffh@qymwmaej.yu","name":"王斌强","phone":"18637986236"}

返回报文:

{    
"name": "王*",    
"phone": "186****6236",    
"email": "*******@qymwmaej.yu"
}

至此,完成了使用注解的方式完成字段的脱敏操作。


版权声明:本文为qq_44942083原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。