在实际项目中通常需要对用户的敏感信息比如身份号、家庭地址、手机号等进行脱敏操作以保证信息安全。
解决方案
-
每个接口单独处理,这种方式操作简单,但是重复工作量非常大,而且后续脱敏规则变更是需要逐个修改。
-
使用定义Jackson注解,在序列化的时候对敏感字段值进行处理,这种方式高效又优雅,省时省力,支持扩展。
这里我们使用注解的方式实现,在序列化的时候对敏感字段值进行处理。
项目结构如下

1. 新建脱敏注解类
在接口返回实体字段添加该注解并指定脱敏规则,接口返回数据当前字段会按规则脱敏。
package com.mfc.test.datamasking;import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;import com.fasterxml.jackson.databind.annotation.JsonSerialize;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@JacksonAnnotationsInside@JsonSerialize(using = DataMaskingJsonSerializer.class)public @interface DataMasking {// 脱敏规则MaskingRule maskingRule();}
2.新建脱敏规则设置类
该类是个枚举值,设置字段的脱敏方式
package com.mfc.test.datamasking;import lombok.Getter;import java.util.function.Function;/*** 脱敏策略,不同数据可选择不同的策略*/@Getterpublic enum MaskingRule {/*** 用户名脱敏*/USERNAME(str -> str.replaceAll("(\\S)\\S(\\S*)", "$1*$2")),/*** 身份证脱敏*/ID_CARD(str -> str.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1****$2")),/*** 手机号脱敏*/MOBILEPHONE(str -> str.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),/*** 地址脱敏*/ADDRESS(str -> str.replaceAll("(\\S{3})\\S{2}(\\S*)\\S{2}", "$1****$2****"));private final Function<String, String> desensitizer;MaskingRule(Function<String, String> desensitizer) {this.desensitizer = desensitizer;}}
3.新建脱敏序列化器
package com.mfc.test.datamasking;import com.fasterxml.jackson.core.JsonGenerator;import com.fasterxml.jackson.databind.BeanProperty;import com.fasterxml.jackson.databind.JsonMappingException;import com.fasterxml.jackson.databind.JsonSerializer;import com.fasterxml.jackson.databind.SerializerProvider;import com.fasterxml.jackson.databind.ser.ContextualSerializer;import java.io.IOException;import java.util.Objects;/*** 序列化器*/public class DataMaskingJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {private MaskingRule maskingRule;@Overridepublic JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {// 获取自定义注解DataMasking annotation = beanProperty.getAnnotation(DataMasking.class);// 注解不为空,且标注的字段为Stringif (Objects.nonNull(annotation) && Objects.equals(String.class, beanProperty.getType().getRawClass())) {this.maskingRule = annotation.maskingRule();return this;}// 注解为空,字段不为String,寻找合适的序列化器进行处理return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);}@Overridepublic void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {if (Objects.isNull(maskingRule)) {// 定义规则为空,返回原字符串jsonGenerator.writeString(s);} else {// 定义规则不为空,返回处理后字符串jsonGenerator.writeString(maskingRule.getDesensitizer().apply(s));}}}
4.新建实体并为要脱敏的字段添加注解
package com.mfc.test.domain;import com.mfc.test.datamasking.DataMasking;import com.mfc.test.datamasking.MaskingRule;import lombok.Data;import lombok.ToString;import lombok.experimental.Accessors;@ToString@Accessors(chain = true)@Datapublic class Member {private String username;private String gender;@DataMasking(maskingRule = MaskingRule.USERNAME)private String realName;@DataMasking(maskingRule = MaskingRule.ID_CARD)private String idCard;@DataMasking(maskingRule = MaskingRule.MOBILEPHONE)private String mobilePhone;@DataMasking(maskingRule = MaskingRule.ADDRESS)private String address;}
5.新建controller
package com.mfc.test.controller;import com.mfc.test.domain.Member;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/test")public class TestController {@GetMapping("/getMember")public Member test() {Member member = new Member();member.setUsername("zxh").setGender("男").setRealName("张小华").setIdCard("610122199003142113").setMobilePhone("15111111111").setAddress("四川省成都市武侯区武侯大街1033号");return member;}}
访问http://localhost:8081/test/getMember
测试结果如下


3069

被折叠的 条评论
为什么被折叠?



