3

I need a handy way to censore DTO fields/secrets when converting them into a json string (for logging).

Im using objectmapper and I heard about @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) but it completely hides the annotated field and it can be confusing because if I want to trace an error in the logs I might think that it was just null, therefore It would be better if the property's value was simply replaced with something like ***.

Defining a list of field names to replace values is also not suitable because i might censore fields that i dont want to. Also refactoring would complicate things too.

My idea is to annotate fields with a custom annotation and implement some json postprocessing with objectmapper but i couldnt find a way to do it. For example:

public record Person(String name, @Censored String secret) {
}

// and later
log.info(objectMapper.writeValueAsString(person));

The output should be

{"name":"John","secret":"***"}

Is there a way to configure ObjectMapper to censore fields that are annotated with my custom @Censored annotation?

Im also open to other suggestions to implement log censoring logic.

I tried to use @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) but it didnt achieve the result that i wanted.

1
  • You wouldn't do the masking in ObjectMapper, otherwise, how would you ever read the field even with code? You'd use a masking pattern in your logback configuration: baeldung.com/logback-mask-sensitive-data. Either way, a bad idea to pass around passwords in plain text. Commented Apr 11, 2024 at 18:17

1 Answer 1

2

The simplest way I can think to implement this via Jackson would be with a custom serializer:

public class CensoredSerializer<T> extends StdSerializer<T> {
    public CensoredSerializer() {
        this(null);
    }

    public CensoredSerializer(Class<T> t) {
        super(t);
    }

    @Override
    public void serialize(T value, JsonGenerator gen, SerializerProvider provider)
            throws IOException
    {
        gen.writeString("***");
    }
}

You would use this like so:

public record Person(String name, @JsonSerialize(using = CensoredSerializer.class) String secret) {
}

If that's too verbose for you, you can use it as the basis for a custom annotation:

@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = CensoredSerializer.class)
public @interface Censored {
}

This lets you use @Censored as a shorthand for @JsonSerialize(using = CensoredSerializer.class), like so:

public record Person(String name, @Censored String secret) {
}

As mentioned in the comments, it is also possible to do this by masking values in the logging config. I personally feel like there's some value in having it defined directly in the class instead, but your milage may vary!

Sign up to request clarification or add additional context in comments.

2 Comments

One more thing: is there a way to configure ObjectMapper to use default serializer in some senarios? The problem is that this serializer will get registered into every objectmapper instance but i only need it when im logging.
@BaksaZoltán Not sure myself - some of the answers here look promising, though: stackoverflow.com/questions/14056716/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.