在spring boot中利用自定义注解+hutool工具+自定义序列化器来实现注解脱敏(动态控制数据脱敏处理的实现)
1、引言
在保护用户隐私的问题上,数据脱敏是一种有效的手段。然而,有时我们需要在运行时动态决定是否进行脱敏,这就需要我们提供一种灵活的、可配置的数据脱敏处理机制。
本文将介绍如何在 Spring 框架中实现这种机制。我们将定义一个新的注解 EnableDesensitize,并在需要脱敏的 Controller 方法上使用它。然后,我们会通过拦截器检查这个注解,并将脱敏的标志存储在 RequestContextHolder 中。最后,在序列化器中检查这个标志,决定是否进行脱敏。
此外,我们还会使用 hutool 工具库来进行数据脱敏,这是一个包含了许多实用功能的 Java 工具类库。
通过这篇文章,你将能理解和实现动态控制数据脱敏的方法,更好地保护用户隐私。
2、 使用hutool工具进行数据脱敏
Hutool 是一个小而全的 Java 工具类库,它为 Java 开发者提供了一系列的工具,包括文件、日期、加密、数据脱敏等功能。在这里,我们将使用 Hutool 的数据脱敏功能来实现我们的数据脱敏处理。
Hutool 提供了DesensitizedUtil类,其中包含了一系列的静态方法,可以用来对各种类型的数据进行脱敏。例如,我们可以使用 DesensitizedUtil.email 方法来对电子邮件地址进行脱敏,或者使用 DesensitizedUtil.bankCard 方法来对银行卡号进行脱敏。
以下是一个自定义的脱敏类型枚举:
public enum DesensitizedType {
/**
* 用户id
*/
USER_ID,
/**
* 中文名
*/
CHINESE_NAME,
/**
* 身份证号
*/
ID_CARD,
/**
* 座机号
*/
FIXED_PHONE,
/**
* 手机号
*/
MOBILE_PHONE,
/**
* 地址
*/
ADDRESS,
/**
* 电子邮件
*/
EMAIL,
/**
* 密码
*/
PASSWORD,
/**
* 中国大陆车牌,包含普通车辆、新能源车辆
*/
CAR_LICENSE,
/**
* 银行卡
*/
BANK_CARD,
/**
* IPv4地址
*/
IPV4,
/**
* IPv6地址
*/
IPV6,
/**
* 定义了一个first_mask的规则,只显示第一个字符。
*/
FIRST_MASK,
/**
* 清空为null
*/
CLEAR_TO_NULL,
/**
* 清空为""
*/
CLEAR_TO_EMPTY,
/**
* 默认不操作
*/
DEFAULT,
/**
* 自定义规则
*/
CUSTOMIZE_RULE
}
这些脱敏类型用在脱敏注解上:
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveInfoSerializer.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Desensitization {
/**
* 脱敏类型
*/
DesensitizedType type();
/**
* 前置不需要打码的长度
*/
int prefixLen() default 0;
/**
* 后置不需要打码的长度
*/
int suffixLen() default 0;
}
使用了以下注解修饰:
@JacksonAnnotationsInside:表示该注解是一个 Jackson 注解,并可以作用在其他 Jackson 注解上。
@JsonSerialize(using = SensitiveInfoSerializer.class):表示使用 SensitiveInfoSerializer 类来对被标记的字段进行序列化处理,将敏感信息进行脱敏处理。
@Retention(RetentionPolicy.RUNTIME):表示该注解在运行时仍然可用。
@Target(ElementType.FIELD):表示该注解可以作用在字段上。
在实体类中使用,例如User中:
@Data
public class Student {
@Desensitization(type = DesensitizedType.USER_ID)
private String studentId;
@Desensitization(type = DesensitizedType.CHINESE_NAME)
private String name;
private int age;
@Desensitization(type = DesensitizedType.CUSTOMIZE_RULE,prefixLen = 3,suffixLen = 6)
private String department;
@Desensitization(type = DesensitizedType.MOBILE_PHONE)
private String phoneNumber;
@Desensitization(type = DesensitizedType.EMAIL)
private String email;
@Desensitization(type = DesensitizedType.ADDRESS)
private String address;
}
3、定义脱敏注解 EnableDesensitize
在 Spring 中,我们可以通过自定义注解在Controller层标记需要脱敏的方法。这里,我们将定义一个名为 EnableDesensitize 的注解,用于标记需要进行数据脱敏的 Controller 方法。
下面是 EnableDesensitize 注解的定义:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableDesensitize {
}
这个注解的定义中,@Target 指定了这个注解可以应用到的 Java 元素类型,这里我们允许它应用到方法(ElementType.METHOD)。@Retention 则指定了这个注解的生命周期,RetentionPolicy.RUNTIME 意味着这个注解在运行时仍然有效,这样我们就可以在运行时通过反射来检查这个注解。
有了这个注解,我们就可以在 Controller 方法上使用它来标记需要进行数据脱敏的方法,如下所示:
@EnableDesensitize
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
// ...
}
在这个例子中,getUser() 方法被标记为需要进行数据脱敏,因此当这个方法被调用时,我们需要对其返回的 User 对象进行脱敏处理。
4、实现脱敏拦截器
在 Spring 中,我们可以通过实现 HandlerInterceptor 接口来创建一个拦截器,这个拦截器可以在处理 HTTP 请求之前、之后或者在视图渲染之前进行拦截操作。在这个例子中,我们将创建一个拦截器,用于在处理请求之前检查 EnableDesensitize 注解,并将这个信息存储在 RequestContextHolder 中。
首先,我们需要创建一个新的类,这个类需要实现 HandlerInterceptor 接口,并覆盖 preHandle 方法:
public class DesensitizeInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
EnableDesensitize annotation = handlerMethod.getMethodAnnotation(EnableDesensitize.class);
if (annotation != null) {
// 如果方法上有 EnableDesensitize 注解,那么将一个标记存入 RequestContextHolder
RequestContextHolder.getRequestAttributes().setAttribute("enableDesensitize", true, RequestAttributes.SCOPE_REQUEST);
}
}
return true;
}
}
在这个 preHandle 方法中,我们首先检查 handler 是否是 HandlerMethod 的实例。如果是,那么我们就可以通过 getMethodAnnotation 方法来获取方法上的 EnableDesensitize 注解。如果这个注解存在,那么我们就将一个名为 “enableDesensitize” 的标记存入 RequestContextHolder。
然后,我们需要在 Spring 的配置中注册这个拦截器:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new DesensitizeInterceptor());
}
}
在这个配置中,我们通过 addInterceptor 方法将我们的 DesensitizeInterceptor 添加到了拦截器的注册表中。这样,每次处理 HTTP 请求时,都会先调用这个拦截器的 preHandle 方法。
有了这个拦截器,我们就可以在处理请求之前检查 EnableDesensitize 注解,并将这个信息存储在 RequestContextHolder 中,以便在之后的处理中使用。
5、实现脱敏序列化器
在 Spring 中,我们可以通过实现 JsonSerializer 接口并结合 ContextualSerializer 接口来创建一个自定义的序列化器。在这个例子中,我们将创建一个序列化器,用于在序列化数据时进行脱敏处理。
首先,我们需要创建一个新的类,这个类需要继承 StdSerializer<String> 并实现 ContextualSerializer 接口,覆盖 createContextual 和 serialize 方法:
public class SensitiveInfoSerializer extends JsonSerializer<String> implements ContextualSerializer {
private DesensitizedType type;
private int prefixLen;
private int suffixLen;
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
boolean enableDesensitize = false;
if (requestAttributes != null) {
enableDesensitize = Convert.toBool(requestAttributes.getAttribute("enableDesensitize", RequestAttributes.SCOPE_REQUEST), false);
}
if (enableDesensitize && value != null && type != null) {
String newStr = value;
switch (type) {
case USER_ID:
newStr = String.valueOf(DesensitizedUtil.userId());
break;
case CHINESE_NAME:
newStr = DesensitizedUtil.chineseName(value);
break;
case ID_CARD:
newStr = DesensitizedUtil.idCardNum(value, 1, 2);
break;
case FIXED_PHONE:
newStr = DesensitizedUtil.fixedPhone(value);
break;
case MOBILE_PHONE:
newStr = DesensitizedUtil.mobilePhone(value);
break;
case ADDRESS:
newStr = DesensitizedUtil.address(value, 8);
break;
case EMAIL:
newStr = DesensitizedUtil.email(value);
break;
case PASSWORD:
newStr = DesensitizedUtil.password(value);
break;
case CAR_LICENSE:
newStr = DesensitizedUtil.carLicense(value);
break;
case BANK_CARD:
newStr = DesensitizedUtil.bankCard(value);
break;
case IPV4:
newStr = DesensitizedUtil.ipv4(value);
break;
case IPV6:
newStr = DesensitizedUtil.ipv6(value);
break;
case FIRST_MASK:
newStr = DesensitizedUtil.firstMask(value);
break;
case CLEAR_TO_EMPTY:
newStr = DesensitizedUtil.clear();
break;
case CLEAR_TO_NULL:
newStr = DesensitizedUtil.clearToNull();
break;
case DEFAULT:
newStr = value;
break;
case CUSTOMIZE_RULE:
newStr = StrUtil.hide(value, prefixLen, suffixLen);
break;
}
gen.writeString(newStr);
} else {
gen.writeObject(value);
}
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
if (property != null) {
Desensitization desensitization = property.getAnnotation(Desensitization.class);
if (desensitization != null) {
this.type = desensitization.type();
this.prefixLen = desensitization.prefixLen();
this.suffixLen = desensitization.suffixLen();
}
}
return this;
}
}
这段代码定义了一个脱敏序列化器 SensitiveInfoSerializer,它继承自JsonSerializer<String>并实现了ContextualSerializer接口。这个序列化器可以对字符串类型的字段进行脱敏处理。
以下是对代码的详细解释:
enableDesensitize:决定是否启用脱敏。
type:脱敏的类型,由 DesensitizedType 枚举定义。
prefixLen 和 suffixLen:自定义规则脱敏时,保留的前缀和后缀的长度。
serialize 方法:这是 JsonSerializer 接口的核心方法,用于将对象序列化为 JSON。在这个方法中,如果启用了脱敏并且值不为空,且脱敏类型不为空,那么根据脱敏类型进行脱敏处理。否则,直接将原始值写入 JSON。
createContextual 方法:这是 ContextualSerializer 接口的方法,用于在运行时确定如何序列化字段。在这个方法中,检查字段是否有 Desensitization 注解,如果有,那么获取注解中定义的脱敏类型、前缀长度和后缀长度,并设置到序列化器中。
6、效果
测试接口
@GetMapping("/getUser1")
@EnableDesensitize
public R getUser1(){
User userInfo = getUserInfo();
return R.ok("脱敏后的数据").put("data", userInfo);
}
@GetMapping("/getUser2")
public R getUser2(){
User userInfo = getUserInfo();
return R.ok("未脱敏的数据").put("data", userInfo);
}
private User getUserInfo(){
User user = new User();
user.setStudentId("2002210");
user.setName("张三");
user.setAge(18);
user.setEmail("zhangsan@example.com");
user.setPhoneNumber("13800138000");
user.setDepartment("计算机学院");
user.setAddress("北京市海淀区颐和园路5号");
return user;
}
脱敏后的数据:

未脱敏的数据:

7、总结
没有,你们自己总结吧。
更多推荐
所有评论(0)