1、前言
通过配置数据库需要脱敏的字段,实现对接平台字段的动态脱敏
2、前提
springboot返回浏览器之前默认通过jackson序列化为json返回前端,保证响应流是使用jackson工具将数据序列化,不可使用fastjson序列化,否则@JsonSerialize(using = SensitiveDataSerializer.class)配置不生效。
脱敏code:唯一,指向了平台的字段,code和字段为一一映射关系
规 则:【平台:模块:字段】 模块之前可以有父子级关系
3、编码实现
3.1自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
@JacksonAnnotationsInside
@Target(ElementType.FIELD)
//jackson包的注解 指定自定义序列化类
@JsonSerialize(using = SensitiveDataSerializer.class)
public @interface SensitiveData {
/**
* 脱敏code
*/
String sensitiveCode();
}
3.2 自定义序列化器
@Slf4j
@AllArgsConstructor
@NoArgsConstructor
public class SensitiveDataSerializer extends JsonSerializer<String> implements ContextualSerializer {
private SensitiveData sensitiveData;
//用来获取平台的脱敏配置
private SensitiveTemplate sensitiveTemplate;
/**
* 添加@SensitiveData使用自定义的序列化规则
*
* @param prov prov
* @param property property
* @return JsonSerializer<?>
* @author xulongyu
* @date 2023/9/20 18:35
**/
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
SensitiveData annotation = property.getAnnotation(SensitiveData.class);
SensitiveTemplate bean = SpringUtil.getBean(SensitiveTemplate.class);
if (annotation != null) {
return new SensitiveDataSerializer(annotation, bean);
}
return this;
}
}
/**
* 序列化规则
*
* @param value 原值
* @param gen gen
* @param serializers serializers
* @author xulongyu
* @date 2023/9/20 18:35
**/
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) {
try {
if (StrUtil.isBlank(value) || null == sensitiveData) {
gen.writeString(value);
return;
}
String sensitiveCode = sensitiveData.sensitiveCode();
// 存放原始值
realValMap.put(sensitiveCode, value);
// code:Obj{字段|脱敏规则}
Map<String, SensitiveConfigRes> config = sensitiveTemplate.getConfig();
SensitiveConfigRes sensitiveConfigRes = config.get(sensitiveCode);
if (null == sensitiveConfigRes) {
gen.writeString(value);
return;
}
//脱敏工具类
String newValue = DesensitizeEnum.desensitizeByModel(sensitiveConfigRes.getDesensitizeMode(), value);
gen.writeString(newValue);
} catch (IOException e) {
log.info("序列化错误" + e.getMessage());
} finally {
try {
gen.writeString(value);
} catch (IOException e) {
log.info("序列化错误" + e.getMessage());
}
}
}
3.3 脱敏工具类
/**
* 脱敏枚举工具类
*
* @author xulongyu
* @date 2023/1/6 10:18
**/
public enum DesensitizeEnum {
// 无
NONE(){
@Override
public String desensitize(String content) {
return content;
}
},
//【手机号码】前三位,后四位,其他隐藏,比如135****6810
PHONE() {
@Override
public String desensitize(String content) {
return desValue(content, 3, 4, "*");
}
},
//【地址】只显示到地区,不显示详细地址,比如:北京市海淀区****
ADDRESS() {
@Override
public String desensitize(String content) {
return desValue(content, 6, 0, "*");
}
},
//【身份证号】显示前六位, 四位,其他隐藏。共计18位或者15位,比如:340304*******1234
ID_NUMBER() {
@Override
public String desensitize(String content) {
return desValue(content, 3, 2, "*");
}
},
//【全脱敏】密码的全部字符都用*代替,比如:******
ALL() {
/**
* 根据内容脱敏
*
* @param content content
* @return String
* @author xulongyu
* @date 2023/1/6 10:19
**/
@Override
public String desensitize(String content) {
if (content == null) {
return null;
}
return "******";
}
};
DesensitizeEnum() {
}
/**
* 根据内容脱敏
*
* @param content content
* @return String
* @author xulongyu
* @date 2023/1/6 10:19
**/
public String desensitize(String content) {
return null;
}
/**
* 根据脱敏模式进行脱敏
*
* @param desensitizeMode 脱敏模式
* @param content 内容
* @return String
* @author xulongyu
* @date 2023/1/6 10:22
**/
public static String desensitizeByModel(String desensitizeMode, String content) {
if(StrUtil.isEmpty(content)){
return StrPool.EMPTY;
}
for (DesensitizeEnum value : DesensitizeEnum.values()) {
if (value.name().equals(desensitizeMode)) {
return value.desensitize(content);
}
}
return content;
}
/**
* 对字符串进行脱敏操作
*
* @param origin 原始字符串
* @param prefixNoMaskLen 左侧需要保留几位明文字段
* @param suffixNoMaskLen 右侧需要保留几位明文字段
* @param maskStr 用于遮罩的字符串, 如'*'
* @return 脱敏后结果
*/
public static String desValue(String origin, int prefixNoMaskLen, int suffixNoMaskLen, String maskStr) {
if (origin == null) {
return null;
}
StringBuilder sb = new StringBuilder();
for (int i = 0, n = origin.length(); i < n; i++) {
if (i < prefixNoMaskLen) {
sb.append(origin.charAt(i));
continue;
}
if (i > (n - suffixNoMaskLen - 1)) {
sb.append(origin.charAt(i));
continue;
}
sb.append(maskStr);
}
return sb.toString();
}
}
4.jackson
jackson序列化为json字符串配置
ObjectMapper objectMapper = new ObjectMapper();
// 保留null,空数组、空map 序列化
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);