I am getting an encrypted String as Query parameter to a Spring rest controller method.
I wanted to decrypt the string before it reaches the method based on some annotat
A custom implementation of org.springframework.format.Formatter is a valid approach for this use case. This is how Spring itself implements formatters for dates, currencies, number styles etc.
Steps:
Declare an annotation: Decrypt:
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
public @interface Decrypt {
}
Declare an AnnotationFormatterFactory which uses the new annotation:
import org.springframework.context.support.EmbeddedValueResolutionSupport;
import org.springframework.format.AnnotationFormatterFactory;
import org.springframework.format.Formatter;
import org.springframework.format.Parser;
import org.springframework.format.Printer;
import java.text.ParseException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
public class DecryptAnnotationFormatterFactory extends EmbeddedValueResolutionSupport
implements AnnotationFormatterFactory {
@Override
public Set> getFieldTypes() {
Set> fieldTypes = new HashSet<>();
fieldTypes.add(String.class);
return Collections.unmodifiableSet(fieldTypes);
}
@Override
public Printer getPrinter(Decrypt annotation, Class> fieldType) {
return configureFormatterFrom(annotation);
}
@Override
public Parser getParser(Decrypt annotation, Class> fieldType) {
return configureFormatterFrom(annotation);
}
private Formatter configureFormatterFrom(Decrypt annotation) {
// you could model something on the Decrypt annotation for use in the decryption call
// in this example the 'decryption' call is stubbed, it just reverses the given String
// presumaby you implementaion of this Formatter will be different e.g. it will invoke your encryption routine
return new Formatter() {
@Override
public String print(String object, Locale locale) {
return object;
}
@Override
public String parse(String text, Locale locale) throws ParseException {
return new StringBuilder(text).reverse().toString();
}
};
}
}
Register this formatter factory with your web context:
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class WebConfigurer extends WebMvcConfigurerAdapter {
@Override
public void addFormatters(FormatterRegistry registry) {
super.addFormatters(registry);
registry.addFormatterForFieldAnnotation(new DecryptAnnotationFormatterFactory());
}
}
That's it.
With the above in place, all usages of a @RequestParam which are qualified with @Decrypt will be passed through the parse() method declared in DecryptAnnotationFormatterFactory so you can implement your decryption call there.
To prove this, the following test passes:
@RunWith(SpringRunner.class)
@WebMvcTest(controllers = YourController.class)
public class YourControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void theSecretRequestParameterWillBeConverted() throws Exception {
MvcResult mvcResult = mockMvc.perform(get("/customer?secret=abcdef")).andExpect(status().isOk()).andReturn();
// the current implementation of the 'custom' endpoint returns the value if the secret request parameter and
// the current decrypt implementation just reverses the given value ...
assertThat(mvcResult.getResponse().getContentAsString(), is("fedcba"));
}
}