使用jackson进行json序列化时进行敏感字段脱敏(加密)或者忽略

需求:

1、通过注解的方式指定字段在序列化时进行脱敏或者加密;
2、通过注解的方式指定字段在序列化时忽略掉;
3、某些情况下需要处理的类不是我们可以修改的,但是也要实现上述两项需求;

实现如下:

工具类SensitiveJsonUtil:

package com.example.jackson;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

public class SensitiveJsonUtil {

    private static ObjectMapper objectMapper = new ObjectMapper();

    static {
        // 反序列化时忽略不存在的字段
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        // 注册处理敏感字段的扩展模块
        objectMapper.registerModule(new SensitiveFieldProcessModule());

        // 通过mixIn功能来按字段名忽略一些字段
        objectMapper.addMixIn(Object.class, IgnoreSensitiveFieldsMixin.class);
    }

    @JsonIgnoreProperties(
            value = {
                    "password",
                    "secret",
                    "token"
            }
    )
    static class IgnoreSensitiveFieldsMixin {
    }

    public static String toJsonString(Object object) {
        try {
            return objectMapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(String.format("toJsonString error, %s", e.getMessage()), e);
        }
    }

}

扩展模块类SensitiveFieldProcessModule(这里仅为demo,所以一些相关的类直接以嵌套类放在了一起)

package com.example.jackson;

import com.fasterxml.jackson.annotation.JacksonAnnotation;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.core.util.VersionUtil;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.type.MapType;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;

public class SensitiveFieldProcessModule extends Module {

    private static final String MODULE_NAME = "jackson-sensitive-field-process-module";

    private Version version = VersionUtil.parseVersion("0.0.1", "com.example", MODULE_NAME);

    @Override
    public String getModuleName() {
        return MODULE_NAME;
    }

    @Override
    public Version version() {
        return version;
    }

    @Override
    public void setupModule(SetupContext setupContext) {
        setupContext.addBeanSerializerModifier(new SensitiveFieldModifier());
    }

    public static class SensitiveFieldModifier extends BeanSerializerModifier {

        @Override
        public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
            List<BeanPropertyWriter> newWriters = new ArrayList<>();
            for (BeanPropertyWriter writer : beanProperties) {
                if (writer.getAnnotation(Sensitive.class) != null && writer.getType().isTypeOrSubTypeOf(String.class)) {
                    // 如果带有 @Sensitive 注解,并且是字符串,则使用自定义处理
                    JsonSerializer<Object> serializer = new SensitiveJsonSerializer(writer.getSerializer());
                    writer.assignSerializer(serializer);

                }
                newWriters.add(writer);
            }

            return newWriters;

            // super.changeProperties(config, beanDesc, beanProperties);
        }

        @Override
        public JsonSerializer<?> modifyMapSerializer(SerializationConfig config, MapType valueType, BeanDescription beanDesc, JsonSerializer<?> serializer) {
            return super.modifyMapSerializer(config, valueType, beanDesc, serializer);
        }

    }

    @JacksonAnnotation
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD})
    public @interface Sensitive {
    }

    public static class SensitiveJsonSerializer extends JsonSerializer<Object> {

        private final JsonSerializer<Object> serializer;

        public SensitiveJsonSerializer(JsonSerializer<Object> serializer) {
            this.serializer = serializer;
        }

        @Override
        public void serialize(Object object, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
            if (object != null && object instanceof String) {
                String str = (String) object;
                if (StringUtils.isNotBlank(str)) {
                    object = processSensitiveField(str);
                }
            }

            if (this.serializer == null) {
                serializerProvider.defaultSerializeValue(object, jsonGenerator);
            } else {
                this.serializer.serialize(object, jsonGenerator, serializerProvider);
            }
        }

        private static String processSensitiveField(String input) {
            if (StringUtils.isBlank(input)) {
                return input;
            }

            input = StringUtils.trim(input);
            int strLen = input.length();

            if (strLen <= 1) {
                return "*";
            } else if (strLen == 2) {
                return String.format("%s*", input.charAt(0));
            } else if (strLen == 3) {
                return String.format("%s*%s", input.charAt(0), input.charAt(strLen - 1));
            } else {
                int left = strLen / 4;
                int right = strLen / 3;
                return String.format("%s%s%s",
                        StringUtils.left(input, left),
                        StringUtils.repeat('*', (strLen - left - right)),
                        StringUtils.right(input, right));
            }

        }
    }

}

使用示例:

package com.example;

import com.example.jackson.SensitiveFieldProcessModule;
import com.example.jackson.SensitiveJsonUtil;

import java.util.HashMap;
import java.util.Map;

public class App {

    public static void main(String[] args) {
        Person person = new Person();
        person.setUsername("张一二");
        person.setIdNumber("1000000000112245");
        person.setPassword("123456(password)");
        Map<String, String> otherInfo = new HashMap<>();
        otherInfo.put("nation", "CN");
        otherInfo.put("secret", "(secret)");
        person.setOtherInfo(otherInfo);
        System.out.println(SensitiveJsonUtil.toJsonString(person));
        // 输出:{"username":"张一二","idNumber":"1000*******12245","otherInfo":{"nation":"CN"}}
    }

    static class Person {

        private String username;

        @SensitiveFieldProcessModule.Sensitive
        private String idNumber;

        private String password;

        private Map<String, String> otherInfo;

        // ... 省略getter/setter
    }
}

参考文档:

  1. 使用Jackson加密/解密JSON字段 : 通过自定义模块来进行指定字段的加解密
  2. Serialize Only Fields that meet a Custom Criteria with Jackson
  3. baeldung jackson教程
  4. jackson官网文档链接
  5. jackson官网文档-JacksonMixInAnnotations
posted @ 2021-11-14 17:34  liqipeng  阅读(4600)  评论(0编辑  收藏  举报