1

I am trying to write Jackson deserializer module in Spring Boot app. The main reason is to encrypt pin number from incoming request by using custom Jackson deserializer. Encryption properties are provided by spring component CipherInterface

I was trying solution from this but my custom deserializer still was not called. Instead of this based StringDeserializer is always called and no encryption is performed

Thanks in advance

Annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotation
public @interface Encrypted {
}

Request body with field to be encrypted

@Value
public class CardCounterDecreaseRequest {
    @Encrypted
    private final String pinValue;
}

Jackson configuration

    @Bean
    ObjectMapper unrestrictObjectMapper(final CipherInterface cipherInterface) {
        return JsonMapper.builder()
                .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
                .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
                .enable(SerializationFeature.INDENT_OUTPUT)
                .enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
                .enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)
                .disable(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY)
                .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
                .disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
                .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
                .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                .disable(DeserializationFeature.ACCEPT_FLOAT_AS_INT)
                .visibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE)
                .visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
                .addModules(new EncryptionModule(cipherInterface), new JavaTimeModule(), new VavrModule(), new ParanamerModule())
                .build();
    }

Module:

public class EncryptionModule extends SimpleModule {

    private final CipherInterface cipher;


    public EncryptionModule(final CipherInterface cipher) {
        super();
        this.cipher = cipher;
    }

    @Override
    public void setupModule(final SetupContext context) {
        context.addBeanDeserializerModifier(new EncryptedDeserializerModifier(cipher));
    }

}

public class EncryptedDeserializerModifier extends BeanDeserializerModifier {

    private final CipherInterface cipher;

    public EncryptedDeserializerModifier(final CipherInterface cipher) {
        super();
        this.cipher = cipher;
    }

    @Override
    public BeanDeserializerBuilder updateBuilder(final DeserializationConfig config,
                                                 final BeanDescription beanDesc,
                                                 final BeanDeserializerBuilder builder) {

        final Iterator<SettableBeanProperty> it = builder.getProperties();

        while (it.hasNext()) {
            final SettableBeanProperty prop = it.next();
            if (null != prop.getAnnotation(Encrypted.class)) {
                final JsonDeserializer<Object> current = prop.getValueDeserializer(); // always  returns null
                final EncryptedJsonDeserializer encryptedDeserializer = new EncryptedJsonDeserializer(cipher, current);
                final SettableBeanProperty propWithEncryption = prop.withValueDeserializer(encryptedDeserializer);
                builder.addOrReplaceProperty(propWithEncryption, true);
            }
        }
        return builder;
    }

}

And finally deserializer:

public class EncryptedJsonDeserializer extends JsonDeserializer<Object> implements ContextualDeserializer {

    private final CipherInterface service;
    private final JsonDeserializer<Object> baseDeserializer;
    private final BeanProperty property;

    public EncryptedJsonDeserializer(final CipherInterface service, final JsonDeserializer<Object> baseDeserializer) {
        this.service = service;
        this.baseDeserializer = baseDeserializer;
        this.property = null;
    }

    public EncryptedJsonDeserializer(final CipherInterface service, final JsonDeserializer<Object> wrapped, final BeanProperty property) {
        this.service = service;
        this.baseDeserializer = wrapped;
        this.property = property;
    }

    @Override
    public Object deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException, JsonProcessingException {
        JsonDeserializer<?> deserializer = baseDeserializer;
        if (deserializer instanceof ContextualDeserializer) {
            deserializer = ((ContextualDeserializer) deserializer).createContextual(ctxt, property);
        }
        return // encryption logic here
    }

    @Override
    public JsonDeserializer<?> createContextual(final DeserializationContext ctxt, final BeanProperty property) throws JsonMappingException {
        JsonDeserializer<Object> wrapped = ctxt.findRootValueDeserializer(property.getType());
        return new EncryptedJsonDeserializer(service, wrapped, property);
    }

1 Answers1

0

Just try below code, as you had created deserializer correctly but you are not informing spring that while deserialize this entity use below Custom desierializer class. Add this extra line @JsonDeserialize(using = EncryptedJsonDeserializer.class) and try once.

@Value
@JsonDeserialize(using = EncryptedJsonDeserializer.class)
public class CardCounterDecreaseRequest {
    @Encrypted
    private final String pinValue;
}

It will help you.

Sagar Gangwal
  • 7,544
  • 3
  • 24
  • 38