When serializing with Jackson and logging it, I wanted to crush sensitive data such as authentication tokens, and I wanted to easily specify it with annotations, so I searched.
Like this, replace the property with @Sensitive
in the POOJO field with an appropriate string:
@lombok.Value
public class SomeRequest {
/ ** Properties that don't need to be hidden * /
private final String userId;
/ ** Sensitive information * /
@Sensitive
private final String token;
}
You need three classes: an implementation of the replacement process, an association with the annotation on which the implementation works, and a module that registers them in ObjectMapper.
Class that processes annotated fields:
public class SensitiveFieldMaskingSerializer extends StdSerializer<Object> {
private static final long serialVersionUID = 3888199957574169748L;
protected SensitiveFieldMaskingSerializer() {
super(Object.class);
}
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
// Processing to replace appropriately
if (value instanceof Number) {
gen.writeNumber(0);
} else if (value instanceof String) {
gen.writeString("Hidden: Sensitive string");
} else {
gen.writeNull();
}
}
}
Class that associates the annotation that the implementation of ↑ invokes:
public class SensitiveFieldMaskingAnnotationIntrospector extends NopAnnotationIntrospector {
private static final long serialVersionUID = -4171975975956047379L;
@Override
public Object findSerializer(Annotated am) {
if (am.hasAnnotation(Sensitive.class)) {
return SensitiveFieldMaskingSerializer.class;
}
return null;
}
}
Processing class to register ↑ in ObjectMapper:
static class SensitiveFieldMaskingModule extends Module {
@Override
public String getModuleName() {
return getClass().getSimpleName();
}
@Override
public Version version() {
return Version.unknownVersion();
}
@Override
public void setupModule(SetupContext context) {
context.insertAnnotationIntrospector(new SensitiveFieldMaskingAnnotationIntrospector());
}
}
This is all the implementation you need, so let's use it:
public final class Main {
public static void main(String[] args) throws Exception {
ObjectMapper logObjectMapper = new ObjectMapper()
.registerModule(new SensitiveFieldMaskingModule());
FooRequest fooRequest = new FooRequest ("The one you can leave in the log", "The one you can't leave in the log");
System.out.println ("Serialization result:" + logObjectMapper.writeValueAsString (fooRequest));
// Serialization result: {"userId": "The one you can leave in the log", "token": "Hidden: Sensitive string"}
System.out.println ("Conversion result to Map:" + logObjectMapper.convertValue (fooRequest, Map.class));
// Converted to Map Result: {userId = Guy you can leave in the log, token = Hidden: Sensitive string}
}
}
You can see that token
is replaced with another string in both outputs.
I've linked it up, but I've put a working one in gist.
As an aside, when you need an ObjectMapper that is different from the normal one like this time, you can avoid unnecessary troubles by preparing it as a new log instead of using the ObjectMapper used elsewhere. If you're using a DI container, don't bother and define it as a named bean.
Recommended Posts