使用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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章