2020-03-08

思路
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项目

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,961评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,444评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,009评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,082评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,101评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,271评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,738评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,395评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,539评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,434评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,481评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,160评论 3 317
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,749评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,816评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,038评论 1 256
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,548评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,140评论 2 341

推荐阅读更多精彩内容

  • 【一句话推荐】 每个时代,每个人都有自己的疯病。 【作者简介】 弗朗索瓦•勒洛尔,是一位在法国和美国都享誉盛名的精...
    YOLANDA_7314阅读 1,261评论 0 0
  • 现代艺术大师达利和毕加索的画好在哪里? 10多年前,看着《格尔尼卡》,还是个中学生的我问过同样的问题,“你太年轻,...
    Kecvin阅读 876评论 0 1
  • 第49天 爱情的定义 哈喽大家好!我叫陈爱金!今天是2020年3月8日是我挑战1000天演讲打卡的第49天。今天我...
    陈爱金阅读 136评论 0 0
  • “快到女生节了,帮个忙,跟我们去宣传一下,你主讲我发传单” “??不是妇女节吗?” “女生节!妇女节前一天” 这是...
    卖冰棍的小火柴阅读 2,886评论 0 3
  • 其实蛮恐惧电脑上空白的文档发出的光,刺进心底的空荡。莫名会觉得带着墨香的泛黄纸张所呈现的更有灵魂的味道,深...
    拂尘破晓阅读 260评论 1 3