Add processing to original annotation to Jackson

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.

Thing you want to do

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;
}

manner

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.

end

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

Add processing to original annotation to Jackson
Add original weapons to mods in Minecraft Forge 1.15.2
Add files to jar files
Utilize original marker annotation
Easy to create Processing library
[Java] Creation of original annotation
4 Add println to the interpreter
[Rails] Add column to devise
[Swift] Processing to share screenshots
How to add HDD to Ubuntu