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