自定义Jackson ObjectMapper以读取自定义注释和掩码注释的字段

时间:2016-01-23 15:21:10

标签: java json annotations jackson

我有一个要求,我已经创建了一个自定义注释@MaskSensitiveData。我注释敏感字段。像

class MyBean {
    String userName;
    @MaskSensitiveData
    String cardNumber;
    String abc;
    String xyz;
}

ObjectMapper mapper = new ObjectMapper();
    String json = null;
    AnnotationIntrospector primary = new JaxbAnnotationIntrospector();
    AnnotationIntrospector secondary = new JacksonAnnotationIntrospector();
    AnnotationIntrospector pair = new AnnotationIntrospectorPair(primary, secondary);
    mapper.setAnnotationIntrospector(pair);
    try {
        json = mapper.writeValueAsString(obj);
        /*
         * if(json != null ) { json = getLoggableString(json); }
         */
    } catch (Exception e) {
        return "Unable to convert to Json object:" + obj.toString() + " Message: " + e.getMessage();

    }

我正在使用Jackson ObjectMapper将objct转换为Json。 我需要自定义Object Mapper以屏蔽cardNumber字段以返回json 。 请建议一个更好的方法。

4 个答案:

答案 0 :(得分:4)

以下是使用自定义JsonSerializer解决问题的方法。 从blog post开始遵循步骤。

创建自定义序列化程序

<div class="arrow"></div>

创建一个模块以捆绑序列化程序

public class MaskingSerializer extends JsonSerializer < MyBean > {

  @
  Override
  public void serialize(MyBean value, JsonGenerator jGen, SerializerProvider serializers) throws IOException, JsonProcessingException {
    jGen.writeStartObject();

    Field[] fields = value.getClass().getDeclaredFields();
    for (Field field: fields) {
      field.setAccessible(true);
      MaskSensitiveData mask = field.getDeclaredAnnotation(MaskSensitiveData.class);

      try {
        if (mask != null) {
          field.setAccessible(true);
          field.set(value, field.get(value).toString().replaceAll(".", "*"));
        }

        jGen.writeStringField(field.getName(), field.get(value).toString());


      } catch (IllegalArgumentException e) {
        e.printStackTrace();
      } catch (IllegalAccessException e) {
        e.printStackTrace();
      }
    }

    jGen.writeEndObject();

  }

}

使用ObjectMapper注册模块。

public class MaskingModule extends SimpleModule {
    private static final String NAME = "CustomIntervalModule";
    private static final VersionUtil VERSION_UTIL = new VersionUtil() {};

    public MaskingModule() {
      super(NAME, VERSION_UTIL.version());
      addSerializer(MyBean.class, new MaskingSerializer());
    }
}

测试代码

public class CustomObjectMapper extends ObjectMapper {
    public CustomObjectMapper() {
      registerModule(new MaskingModule());
    }
  }

答案 1 :(得分:2)

package stackoverflow;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import org.hamcrest.Matchers;
import org.junit.Test;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

public class MaskingAnnotationExample {
    // Define @custom Annotation
    // assumed to be used by String type field for this example
    @Retention(RetentionPolicy.RUNTIME)
    static @interface MaskSensitiveData {
    }

    public static class MyBean {
        private String userName;

        @MaskSensitiveData
        private String cardNumber;

        public MyBean() {
        }

        public String getCardNumber() {
            return cardNumber;
        }

        public String getUserName() {
            return userName;
        }

        public void setUserName(String userName) {
            this.userName = userName;
        }

        public void setCardNumber(String cardNumber) {
            this.cardNumber = cardNumber;
        }
    }

    // map the Serializer/Deserializer based on custom annotation
    public static class MaskSensitiveDataAnnotationIntrospector extends NopAnnotationIntrospector {
        private static final long serialVersionUID = 1L;

        @Override
        public Object findSerializer(Annotated am) {
            MaskSensitiveData annotation = am.getAnnotation(MaskSensitiveData.class);
            if (annotation != null) {
                return MaskSensitiveDataSerializer.class;
            }

            return null;
        }

        @Override
        public Object findDeserializer(Annotated am) {
            MaskSensitiveData annotation = am.getAnnotation(MaskSensitiveData.class);
            if (annotation != null) {
                return MaskSensitiveDataDeserializer.class;
            }

            return null;
        }
    }

    public static class MaskSensitiveDataDeserializer extends StdDeserializer<String> {
        private static final long serialVersionUID = 1L;

        public MaskSensitiveDataDeserializer() {
            super(String.class);
        }

        @Override
        public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            // un-masking logic here. in our example we are removing "MASK"
            // string
            String s = p.getValueAsString();
            return s.substring(4);
        }
    }

    public static class MaskSensitiveDataSerializer extends StdSerializer<String> {
        private static final long serialVersionUID = 1L;

        public MaskSensitiveDataSerializer() {
            super(String.class);
        }

        @Override
        public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
            // Masking data; for our example we are adding 'MASK'
            gen.writeString("MASK" + value);
        }
    }

    @Test
    public void demo() throws Exception {
        ObjectMapper mapper = new ObjectMapper();

        AnnotationIntrospector sis = mapper.getSerializationConfig().getAnnotationIntrospector();
        AnnotationIntrospector dis = mapper.getDeserializationConfig().getAnnotationIntrospector();

        AnnotationIntrospector is1 = AnnotationIntrospectorPair.pair(sis, new MaskSensitiveDataAnnotationIntrospector());
        AnnotationIntrospector is2 = AnnotationIntrospectorPair.pair(dis, new MaskSensitiveDataAnnotationIntrospector());

        mapper.setAnnotationIntrospectors(is1, is2);

        MyBean obj = new MyBean();
        obj.setUserName("Saurabh Bhardwaj");
        obj.setCardNumber("4455-7788-9999-7777");
        String json = mapper.writeValueAsString(obj);

        String expectedJson = "{\"userName\":\"Saurabh Bhardwaj\",\"cardNumber\":\"MASK4455-7788-9999-7777\"}";
        assertThat(json, Matchers.is(expectedJson));

        MyBean cloned = mapper.readValue(json, MyBean.class);
        assertThat(cloned.getCardNumber(), is(obj.getCardNumber()));
    }
}

希望这有帮助。

答案 2 :(得分:0)

我使用的是由Spring boot配置的 GENERAL&SHARED ObjectMapper,并且我不希望它被setSerializerFactory()污染或重写整个{{1 }}。这是我的解决方案:

配置ObjectMapper

BeanSerializer

用于屏蔽敏感字段的通用SensitiveDataSerializer

    @Configuration
    @AutoConfigureAfter(JacksonAutoConfiguration.class)
    public static class ExtJacksonConfig {

        @Autowired
        private ObjectMapper objectMapper;

        @PostConstruct
        public void postConstruct() throws JsonMappingException {
            SimpleModule module = new SimpleModule();
            module.addSerializer(ProductOrder.class, new POProductOrderSerializer(
                    (BeanSerializerBase) objectMapper.getSerializerFactory().createSerializer(
                            objectMapper.getSerializerProviderInstance(),
                            objectMapper.getSerializationConfig().constructType(ProductOrder.class))));
            objectMapper.registerModule(module);
        }
    }

最后,您需要的具体序列化器

public class SensitiveDataSerializer<T> extends BeanSerializer {

    private final Function<T, Boolean> authorityChecker;
    private final String maskText;

    public SensitiveDataSerializer(BeanSerializerBase src, Function<T, Boolean> authorityChecker,
                                   String maskText) {
        super(src);
        this.authorityChecker = authorityChecker;
        this.maskText = Optional.ofNullable(maskText).orElse("****");
        assert(this.authorityChecker != null);
        assert(!Checker.isEmpty(sensitiveFieldNames));

        // Replace BeanPropertyWriter
        for (int i=0; i<_props.length; i++) {
            if (_props[i] != null && _props[i].getAnnotation(MaskSensitiveData.class) != null) {
                _props[i] = new SensitivePropertyWriter(_props[i]);
            }
        }

        for (int j=0; j<_filteredProps.length; j++) {
            if (_filteredProps[j] != null && _filteredProps[j].getAnnotation(MaskSensitiveData.class) != null) {
                _filteredProps[j] = new SensitivePropertyWriter(_filteredProps[j]);
            }
        }
    }

    class SensitivePropertyWriter extends BeanPropertyWriter {
        private final BeanPropertyWriter writer;

        SensitivePropertyWriter(BeanPropertyWriter writer) {
            super(writer);
            this.writer = writer;
        }

        @Override
        public void serializeAsField(Object bean,
                                     JsonGenerator gen,
                                     SerializerProvider prov) throws Exception {
            if (authorityChecker.apply((T) bean)) {
                super.serializeAsField(bean, gen, prov);
                return;
            }
            gen.writeStringField(writer.getName(), maskText);
        }
    }
}

答案 3 :(得分:0)

在@Skandya 回答之上,我需要添加前缀和后缀来屏蔽数据。这是我所做的不同实现。

@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveData {
    int prefix() default 0;

    int suffix() default 0;
}

对于 AnnotationIntrospector 的细微差别是传递前缀和后缀,以便序列化程序可以获取它。

import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;

public class SensitiveDataAnnotationIntrospector extends NopAnnotationIntrospector {
    private static final long serialVersionUID = 1L;

    @Override
    public Object findSerializer(Annotated am) {
        SensitiveData annotation = am.getAnnotation(SensitiveData.class);
        if (annotation != null) {
            return new SensitiveDataSerializer(annotation.prefix(), annotation.suffix());
        }

        return null;
    }

    @Override
    public Object findDeserializer(Annotated am) {
        SensitiveData annotation = am.getAnnotation(SensitiveData.class);
        if (annotation != null) {
            return SensitiveDataDeserializer.class;
        }

        return null;
    }
}

这是我的序列化器类,它基于前缀和后缀进行屏蔽

import java.io.IOException;

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

public class SensitiveDataSerializer extends StdSerializer<String> {
    private static final long serialVersionUID = 1L;

    private int prefix = 0;
    private int suffix = 0;

    public SensitiveDataSerializer(int prefix, int suffix) {
        super(String.class);
        this.prefix = prefix;
        this.suffix = suffix;
    }

    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        // Masking data; for our example we are masking all characters'
        gen.writeString(getFormattedString(value));
    }

    private String getFormattedString(String value) {
        return value.substring(0, prefix)
                .concat(value.substring(prefix, value.length() - suffix).replaceAll(".", "*"))
                .concat(value.substring(value.length() - suffix));
    }
}

标签和对象映射器看起来像这样

private class Bean {

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

public void setEmailId(String emailId) {
    this.emailId = emailId;
}

String id;
@SensitiveData(prefix = 1, suffix = 4)
String emailId;

}

测试将是

 @Test
    public void beanMappingTest() throws Exception {

        Bean testBean = new Bean();
        testBean.setId("1234");
        testBean.setEmailId("test@test.com");

        ObjectMapper om = new ObjectMapper();
        AnnotationIntrospector is1 = AnnotationIntrospector.pair(
                om.getSerializationConfig().getAnnotationIntrospector(), new SensitiveDataAnnotationIntrospector());
        AnnotationIntrospector is2 = AnnotationIntrospector.pair(
                om.getDeserializationConfig().getAnnotationIntrospector(), new SensitiveDataAnnotationIntrospector());
        om.setAnnotationIntrospectors(is1, is2);
        String expectedJson = "{\"id\":\"1234\",\"emailId\":\"t********.com\"}";
        String maskedJson = om.writeValueAsString(testBean);
        assertEquals(maskedJson, expectedJson);
    }