背景

  • 因为线上环境日志跑的都是真实数据,有些手机号及姓名都是用户敏感数据,所以需要对日志进行脱敏

  • 平时输出日志都是下面这样,通过序列化工具将用户信息直接打印出来,这样直接暴露了用户信息

    • LOGGER.debug("用户信息:{}", JsonUtils.deserializer(user))
    • 用户信息:{"name":"小刘","id":"1","mobile":"13145671452"}
  • 所以我们的目标是把用户的敏感信息进行加密打印

实现

  • 定义加密字段注解

    1
    2
    3
    4
    5
    6
    7
    @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface JsonLogIgnore {

    boolean value() default true;

    }
  • 将注解放到需要加密的字段,这里我们将手机号加上了此注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class LogTest {

private String name;

private String id;

@JsonLogIgnore
private String mobile;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getMobile() {
return mobile;
}

public void setMobile(String mobile) {
this.mobile = mobile;
}
}
  • 定义日志脱敏工具类

    • 重点关注initObjectMapper方法,这里使用了fasterxmlIntrospector拦截器来对日志进行截胡
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      154
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      package com.songsy.springboot.test.log;

      import com.fasterxml.jackson.annotation.JsonFilter;
      import com.fasterxml.jackson.annotation.JsonInclude;
      import com.fasterxml.jackson.core.JsonProcessingException;
      import com.fasterxml.jackson.core.type.TypeReference;
      import com.fasterxml.jackson.databind.AnnotationIntrospector;
      import com.fasterxml.jackson.databind.DeserializationFeature;
      import com.fasterxml.jackson.databind.ObjectMapper;
      import com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair;
      import com.fasterxml.jackson.databind.ser.PropertyWriter;
      import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
      import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
      import com.fasterxml.jackson.databind.ser.std.MapProperty;
      import org.apache.commons.lang3.StringUtils;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;

      import java.io.IOException;
      import java.util.HashMap;
      import java.util.Map;
      import java.util.Objects;

      /**
      * 日志脱敏工具类
      * 1、使用方法在实体类上加上 {@link JsonLogIgnore} 注解
      * @author songshuiyang
      * @date 2022/11/3 11:25
      */
      public class JsonLogUtils {

      private static final Logger LOGGER = LoggerFactory.getLogger(JsonLogUtils.class);
      private static ObjectMapper mapper = new ObjectMapper();
      private static ObjectMapper ignoreNullMapper = new ObjectMapper();
      private static SimpleFilterProvider filterProvider = new SimpleFilterProvider();
      public static ThreadLocal<String[]> ignoreFieldThreadLocal = new InheritableThreadLocal();

      public JsonLogUtils() {
      }

      static {
      recreateInnerObjectMapper(new HashMap());
      }


      public static void initObjectMapper(ObjectMapper objectMapper, Map<String, Object> jsonProperties) {
      objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
      Boolean ignoreNull = (Boolean)jsonProperties.get("ignoreNull");
      if (ignoreNull != null && ignoreNull) {
      objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
      }

      SimpleBeanPropertyFilter simpleBeanPropertyFilter = new CustomizeLogFilter();
      filterProvider.addFilter("customizeLogFilter", simpleBeanPropertyFilter);
      ignoreNullMapper.setFilterProvider(filterProvider);
      ignoreNullMapper.addMixIn(Map.class, CustomFilterMixIn.class);
      AnnotationIntrospector annotationIntrospector = objectMapper.getSerializationConfig().getAnnotationIntrospector();
      AnnotationIntrospector serAnnotationIntrospector = AnnotationIntrospectorPair.pair(annotationIntrospector, new LogNopAnnotationIntrospector());
      ignoreNullMapper.setAnnotationIntrospector(serAnnotationIntrospector);
      }

      public static synchronized void recreateInnerObjectMapper(Map<String, Object> jsonProperties) {
      ObjectMapper objectMapper = new ObjectMapper();
      initObjectMapper(objectMapper, jsonProperties);
      mapper = objectMapper;
      }

      public static <T> T serializable(String json, Class<T> clazz) {
      if (StringUtils.isEmpty(json)) {
      return null;
      } else {
      try {
      return mapper.readValue(json, clazz);
      } catch (IOException var3) {
      LOGGER.error(var3.getMessage());
      return null;
      }
      }
      }

      public static <T> T serializable(String json, TypeReference reference) {
      if (StringUtils.isEmpty(json)) {
      return null;
      } else {
      try {
      return mapper.readValue(json, reference);
      } catch (IOException var3) {
      LOGGER.error(var3.getMessage());
      return null;
      }
      }
      }

      public static String deserializer(Object json) {
      if (json == null) {
      return null;
      } else {
      try {
      return ignoreNullMapper.writeValueAsString(json);
      } catch (JsonProcessingException var2) {
      LOGGER.error(var2.getMessage());
      return null;
      }
      }
      }

      public static String deserializer(Object json, String... ignoreField) {
      String jsonStr = null;

      try {
      if (!Objects.isNull(ignoreField)) {
      ignoreFieldThreadLocal.set(ignoreField);
      }

      jsonStr = deserializer(json);
      } finally {
      ignoreFieldThreadLocal.remove();
      }

      return jsonStr;
      }

      public static String ignoreNullObjectToJson(Object object) throws IOException {
      return object == null ? null : ignoreNullMapper.writeValueAsString(object);
      }

      public static ObjectMapper getMapper() {
      return mapper;
      }

      public static String deserializerDebugModel(Object json, String... ignoreField) {
      String jsonStr = null;

      try {
      if (!Objects.isNull(ignoreField)) {
      ignoreFieldThreadLocal.set(ignoreField);
      }

      jsonStr = deserializerDebug(json);
      } finally {
      ignoreFieldThreadLocal.remove();
      }

      return jsonStr;
      }

      public static String deserializerDebugModel(Object json) {
      if (Objects.isNull(json)) {
      return null;
      } else {
      try {
      return ignoreNullMapper.writeValueAsString(json);
      } catch (Exception var2) {
      LOGGER.error(var2.getMessage());
      return null;
      }
      }
      }

      public static String deserializerDebug(Object json) {
      if (json == null) {
      return null;
      } else {
      try {
      return ignoreNullMapper.writeValueAsString(json);
      } catch (Exception var2) {
      LOGGER.error(var2.getMessage());
      return null;
      }
      }
      }
      }
  • 工具类其它代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.songsy.springboot.test.log;

import com.fasterxml.jackson.annotation.JsonFilter;

/**
* @author songshuiyang
* @date 2022/11/3 11:28
*/
@JsonFilter("customizeLogFilter")
public class CustomFilterMixIn {

public CustomFilterMixIn() {
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.songsy.springboot.test.log;

import com.fasterxml.jackson.databind.ser.PropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.std.MapProperty;
import org.apache.commons.lang3.StringUtils;

/**
* @author songshuiyang
* @date 2022/11/3 11:29
*/
public class CustomizeLogFilter extends SimpleBeanPropertyFilter {

public CustomizeLogFilter() {
}

@Override
protected boolean include(PropertyWriter writer) {
if (writer instanceof MapProperty) {
MapProperty mapProperty = (MapProperty)writer;
String name = mapProperty.getName();
String[] ignoreFields = (String[])JsonLogUtils.ignoreFieldThreadLocal.get();
if (StringUtils.equalsAnyIgnoreCase(name, ignoreFields)) {
mapProperty.setValue("Mask*****");
}
}

return super.include(writer);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.songsy.springboot.test.log;

import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
import org.apache.commons.lang3.StringUtils;

/**
* @author songshuiyang
* @date 2022/11/3 11:30
*/
public class LogNopAnnotationIntrospector extends NopAnnotationIntrospector {

public static final String GET = "get";

public LogNopAnnotationIntrospector() {
}

@Override
public Object findSerializer(Annotated am) {
String[] ignoreFields = (String[])JsonLogUtils.ignoreFieldThreadLocal.get();
String name = am.getName();
if (ignoreFields != null && !StringUtils.isEmpty(name)) {
name = name.replace("get", "");
}

JsonLogIgnore jsonLogIgnore = (JsonLogIgnore)am.getAnnotation(JsonLogIgnore.class);
return this.extracted(jsonLogIgnore) && !StringUtils.equalsAnyIgnoreCase(name, ignoreFields) ? null : MaskStdSerializer.class;
}

private boolean extracted(JsonLogIgnore jsonLogIgnore) {
return jsonLogIgnore == null || !jsonLogIgnore.value();
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.songsy.springboot.test.log;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

import java.io.IOException;

/**
* @author songshuiyang
* @date 2022/11/3 11:29
*/
public class MaskStdDeserializer extends StdDeserializer<String> {

protected MaskStdDeserializer() {
super(String.class);
}

@Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return "Mask*****";
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.songsy.springboot.test.log;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

import java.io.IOException;

/**
* @author songshuiyang
* @date 2022/11/3 11:30
*/
public class MaskStdSerializer extends StdSerializer<Object> {

public MaskStdSerializer() {
super(Object.class);
}

@Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString("Mask*****");
}
}

结果输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @author songshuiyang
* @date 2022/11/3 11:34
*/
public class JsonLogUtilsTest {

private static final Logger LOGGER = LoggerFactory.getLogger(JsonLogUtilsTest.class);

@Test
public void test1 () {
LogTest user = new LogTest();
user.setId("1");
user.setName("小刘");
user.setMobile("13145671452");

LOGGER.debug("用户信息:{}", JsonLogUtils.deserializer(user));

}

}
1
11:56:38.553 [main] DEBUG com.songsy.springboot.test.JsonLogUtilsTest - 用户信息:{"name":"小刘","id":"1","mobile":"Mask*****"}

参考