思路
1.分析脱敏场景
2.基于Fastjson、Jackson、logback的各种实现
3.总结
文末有代码实现git地址、小星星
一、分析脱敏场景
生产数据,为了保护用户信息,防止用户信息泄露,我们通常需要对数据进行脱敏主要有(手机号、身份证、姓名等)
打印日志脱敏,日志中看到的信息不是完整的比如:183****0001
接口返回信息脱敏,比如用户手机号、银行卡号、身份证等
数据库脱敏存储
题外话:数据库存储目前用的比较多的是密码加密,其它的数据牵扯到查询,加密成本比较高,sharding-jdbc自带数据加密存储及查询,有兴趣的可以了解一下,我们本节主要讲解前两种
二、基于Fastjson、Jackson、logback的各种实现
1、Fastjson实现
实现思路:
自定义注解,可让用户自定义脱敏方式,用于实体类的属性基于ValueFilter进行 属性注解拦截,并多value进行替换脱敏使用json序列化对象是指定自定义序列化Filter
题外话:ValueFilter:对象值过滤器,将要序列化对象的值进行统一
处理
代码实现:
首先自定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Desensitization {
/**
* 脱敏规则类型
* @return
*/
DesensitionType type();
/**
* 附加值, 自定义正则表达式等
* @return
*/
String[] attach() default "";
}
解释:
脱敏规则类型:主要定义了一些常用的类型(手机号、身份证等)
自定义正则表达式,如果常用的不能满足时可自定义
看一下脱敏规则类型
public enum DesensitionType {
/**
* 手机号脱敏
* "(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\\d{8}"
* "(\\d{3})\\d{4}(\\d{4})"
*/
PHONE("mobile", "11位手机号", "(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\\d{4}(\\d{4}}", "$1****$2"),
IDENTITYNO("identityNo", "15或者18身份证号", "(\\w{4})\\w{7,10}(\\w{4})", "$1****$2"),
BANKCARDNO("bankCardNo", "银行卡号", "(\\d{4})\\d*(\\d{4})", "$1****$2"),
REALNAME("realname","真实姓名Json类型","(\"realname\":)(\"[\u4E00-\u9FA5]{1})[\u4E00-\u9FA5]{1,}(\")","$1$2**$3"),
REALNAME2("realname","真实姓名toString类型","(realname=)([\u4E00-\u9FA5]{1})[\u4E00-\u9FA5]{1,}","$1$2**"),
CUSTOM("custom", "自定义正则处理", ""),
TRUNCATE("truncate", "字符串截取处理", ""),
;
String type;
String describe;
String[] regular;
DesensitionType(String type, String describe, String... regular) {
this.type = type;
this.describe = describe;
this.regular = regular;
}
这里主要注意一下第三个参数:是一个数组,通常0位:要脱敏数据的正则匹配,1位:要脱敏成的格式
然后在我们的需要脱敏的对象字段上加上该注解
@Data
public class UserDTO implements Serializable {
@Desensitization(type=DesensitionType.IDENTITYNO)
private String identityNo;
private String name;
private String realname;
}
接下来编写我们的自定义值过滤器,实现ValueFilter,实现方法process()
@Log4j2
public class FastjsonDesensitizeFilter implements ValueFilter,DesensitizeService {
@Override
public Object process(Object object, String name, Object value) {
if (null == value || !(value instanceof String) || ((String) value).length() == 0) {
return value;
}
try {
Field field = object.getClass().getDeclaredField(name);
Desensitization desensitization;
if (String.class != field.getType() || (desensitization = field.getAnnotation(Desensitization.class)) == null) {
return value;
}
;
DesensitionType type = desensitization.type();
List<String> regular=this.desensitize(type,desensitization);
if (regular.size() > 1) {
String match = regular.get(0);
String result = regular.get(1);
if (null != match && result != null && match.length() > 0) {
return ((String) value).replaceAll(match, result);
}
}
} catch (Exception e) {
log.warn("FastJsonDesensitizeFilter the class {} has no field {}", object.getClass(), name);
}
return value;
}
}
解释:
这里目前只支持String类型的value,大家可以根据需要自定义
获取属性上的注解,根据属性得到相应的脱敏规则类型
按照规则类型进行value替换
然后只要我们在使用fastjson进行序列化的时候指定我们的自定义过滤器即可
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
log.info("requestUrl 【{}】 request body 【{}】",request.getURI(),JSONObject.toJSONString(body,new FastjsonDesensitizeFilter()));
return body;
}
打印出来就是这样
requestUrl 【http://localhost:8080/idNo】 request body 【{"code":"000000","data":{"identityNo":"1111****1111","name":"dsf","realname":"张**"},"message":"SUCCESS"}】
2、基于jackson实现数据脱敏
思路跟用fastjson基本一样,只是实现的类不同而已
public class JacksonDesensitize extends JsonSerializer<String> implements ContextualSerializer,DesensitizeService{
private DesensitionType type;
@Override
public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
throws IOException {
if (type!=null){
try {
List<String> regular=this.desensitize(type,null);
if (regular.size() > 1) {
String match = regular.get(0);
String result = regular.get(1);
if (null != match && result != null && match.length() > 0) {
jsonGenerator.writeString ( value.replaceAll(match, result));
}
}
} catch (Exception e) {
log.warn("JacksonDesensitize has no field {}", value);
}
}
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty)
throws JsonMappingException {
type = beanProperty.getAnnotation(Desensitization.class).type();
return this;
}
}
解释:
获取对象属性上的注解,根据属性得到相应的脱敏规则类型
按照规则类型进行value替换
题外话:这里主要牵扯到要继承JsonSerializer重写serialize()方法实现对象序列化,实现ContextualSerializer接口,实现方法,这个主要是拿到属性上的注解
接下来引用我们的自定义序列化器即可,直接在自定义注解上引用即可
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JsonSerialize(using = JacksonDesensitize.class)
@JacksonAnnotationsInside
public @interface Desensitization {
/**
* 脱敏规则类型
* @return
*/
DesensitionType type();
/**
* 附加值, 自定义正则表达式等
* @return
*/
String[] attach() default "";
}
解释:
这里主要看@JsonSerialize(using=JacksonDesensitize.class)就是让json序列化时用我们自定义的
@JacksonAnnotationsInside这个是注解组合出现时用的
我们现在的项目基本都是前后端分离,controller返回的时候一般都是ResponseBody的,正好我们springMVC后台是默认使用Jackson作为序列化的,所以这时候就可以直接使用
返回就是这样的
{
"code": "000000",
"message": "SUCCESS",
"data": {
"identityNo": "1111****1111",
"name": "dsf",
"realname": "张三"
}
}
3、基于logback进行全局日志脱敏
思路
我们先定义需要脱敏的属性名,就是你真正要打印到日志的属性名字
然后继承logback的MessageConverter重写convert方法
通过正则进行身份证、姓名、手机号的匹配
匹配成功后按规则替换
public class LogDesensitizeConverter extends MessageConverter {
/**
* 日志脱敏开关
*/
private static Boolean converterCanRun = Boolean.TRUE;
/**
* 日志脱敏关键字
*/
@Override
public String convert(ILoggingEvent event) {
// 获取原始日志
String oriLogMsg = event.getFormattedMessage();
if (!converterCanRun){
return oriLogMsg;
}
// 获取脱敏后的日志
DesensitionType[] values = DesensitionType.values();
for (DesensitionType value : values) {
if (value.getRegular()!=null && value.getRegular().length>0 && oriLogMsg.contains(value.getType())){
Matcher matcher = Pattern.compile(value.getRegular()[0]).matcher(oriLogMsg);
oriLogMsg = matcher.replaceAll(value.getRegular()[1]);
}
}
return oriLogMsg;
}
}
然后将我们编写完的Converter添加到logback.xml文件引用即可
之后接口用了,日志文件里时这样的
三、总结
SpringMVC默认使用Jackson作为对象序列化,如果想要使用fastjson需要单独配置,然后指定我们的自定义序列化器就可以了
如果单纯的只用fastjson打印日志那么建议在拦截器,或者像本文代码中实现ResponseBodyAdvice去集中打印,不然还要每次都加我们的自定义序列化器
我们使用Jackson和fastjson时使用了自定义注解,当然也可以根据自己的业务提前定义属性值,像logback的方式一样实现也可
复杂的正则很耗cpu但我们的非常简单,并且都有提前过滤
四、现码的代码
git地址:https://gitee.com/carpentor/spring-cloud-example.git
代码里包含了很多,本文主要看desensitize项目