我正在尝试使用杰克逊进行序列化时屏蔽敏感数据。
我尝试使用@JsonSerialize和自定义注释@Mask。
Mask.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Mask {
String value() default "XXX-DEFAULT MASK FORMAT-XXX";
}
Employee.java
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.util.Map;
public class Employee {
@Mask(value = "*** The value of this attribute is masked for security reason ***")
@JsonSerialize(using = MaskStringValueSerializer.class)
protected String name;
@Mask
@JsonSerialize(using = MaskStringValueSerializer.class)
protected String empId;
@JsonSerialize(using = MaskMapStringValueSerializer.class)
protected Map<Category, String> categoryMap;
public Employee() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmpId() {
return empId;
}
public void setEmpId(String empId) {
this.empId = empId;
}
public Map<Category, String> getCategoryMap() {
return categoryMap;
}
public void setCategoryMap(Map<Category, String> categoryMap) {
this.categoryMap = categoryMap;
}
}
Category.java
public enum Category {
@Mask
CATEGORY1,
@Mask(value = "*** This value of this attribute is masked for security reason ***")
CATEGORY2,
CATEGORY3;
}
MaskMapStringValueSerializer.java
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.util.Map;
public class MaskMapStringValueSerializer extends JsonSerializer<Map<Category, String>> {
@Override
public void serialize(Map<Category, String> map, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartObject();
for (Category key : map.keySet()) {
Mask annot = null;
try {
annot = key.getClass().getField(key.name()).getAnnotation(Mask.class);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
if (annot != null) {
jsonGenerator.writeStringField(((Category) key).name(), annot.value());
} else {
jsonGenerator.writeObjectField(((Category) key).name(), map.get(key));
}
}
jsonGenerator.writeEndObject();
}
}
MaskStringValueSerializer.java
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
public class MaskStringValueSerializer extends StdSerializer<String> implements ContextualSerializer {
private Mask annot;
public MaskStringValueSerializer() {
super(String.class);
}
public MaskStringValueSerializer(Mask logMaskAnnotation) {
super(String.class);
this.annot = logMaskAnnotation;
}
public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (annot != null && s != null && !s.isEmpty()) {
jsonGenerator.writeString(annot.value());
} else {
jsonGenerator.writeString(s);
}
}
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
Mask annot = null;
if (beanProperty != null) {
annot = beanProperty.getAnnotation(Mask.class);
}
return new MaskStringValueSerializer(annot);
}
}
MaskValueTest.java
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.Map;
public class MaskValueTest {
public static void main(String args[]) throws Exception{
Employee employee = new Employee();
employee.setName("John Doe");
employee.setEmpId("1234567890");
Map<Category, String> catMap = new HashMap<>();
catMap.put(Category.CATEGORY1, "CATEGORY1");
catMap.put(Category.CATEGORY2, "CATEGORY2");
catMap.put(Category.CATEGORY3, "CATEGORY3");
employee.setCategoryMap(catMap);
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(employee));
}
}
输出-
{
"name" : "*** The value of this attribute is masked for security reason ***",
"empId" : "XXX-DEFAULT MASK FORMAT-XXX",
"categoryMap" : {
"CATEGORY1" : "XXX-DEFAULT MASK FORMAT-XXX",
"CATEGORY2" : "*** The value of this attribute is masked for security reason ***",
"CATEGORY3" : "CATEGORY3"
}
}
我正在寻找一个基于注释的解决方案,在这里我可以使用通过JsonSerializers初始化的ObjectMapper
的两个不同实例。
答案 0 :(得分:0)
您可以创建模块来捆绑序列化程序,并在需要时向objectmapper注册该模块,而不是使用MaskStringValueSerializer.java,这最终将允许您拥有两个不同的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 MaskMapStringValueSerializer());
}
}
向ObjectMapper注册模块并使用它
ObjectMapper objectMapper = new ObjectMapper().registerModule(new MaskingModule());
System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(employee));
您还可以扩展对象映射器,注册模块并使用它
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
registerModule(new MaskingModule());
}
}
CustomObjectMapper customObjectMapper = new CustomObjectMapper ();
System.out.println(customObjectMapper .writerWithDefaultPrettyPrinter().writeValueAsString(employee));
答案 1 :(得分:0)
删除@JsonSerialize
批注,并将如何处理@Mask
批注的逻辑放在Module
中,例如让它添加一个AnnotationIntrospector
。
您现在可以选择是否呼叫registerModule(Module module)
。
关于编写模块,我将由您自己决定。如果对此有任何疑问,请询问另一个问题。
答案 2 :(得分:0)
为什么不使用两个参数,一个用于原始值,一个用于掩码值。例如,在这种情况下,您可以使用字符串名称和字符串maskedName。然后可以使用掩码值进行记录。
答案 3 :(得分:0)
这可以是 Andreas 建议的实现:
创建一个从 MaskAnnotationIntrospector
扩展的类 JacksonAnnotationIntrospector
并覆盖其 findSerializer
方法,如下所示:
public class MaskAnnotationIntrospector extends JacksonAnnotationIntrospector {
@Override
public Object findSerializer(Annotated am) {
Mask annotation = am.getAnnotation(Mask.class);
if (annotation != null)
return MaskingSerializer.class;
return super.findSerializer(am);
}
}
因此,您可以有两个 ObjectMapper 实例。将 MaskAnnotationIntrospector
添加到您想要屏蔽的位置(例如用于日志记录):
mapper.setAnnotationIntrospector(new MaskAnnotationIntrospector());
MaskAnnotationIntrospector
未设置的另一个实例,在序列化期间不要屏蔽任何实例。
附言MaskAnnotationIntrospector
可以从 JacksonAnnotationIntrospector
和 NopAnnotationIntrospector
扩展,但后者没有为 findSerializer
方法提供任何实现并且调用 super.findSerializer(am)
只是返回 null 并作为直接结果,其他Jackson注解(如@JsonIgnore
)被丢弃,但通过使用前者,这个问题解决了