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 annotation (say @Decrypt
) like below
@RequestMapping(value = "/customer", method = RequestMethod.GET)
public String getAppointmentsForDay(@RequestParam("secret") @Decrypt String customerSecret) {
System.out.println(customerSecret); // Needs to be a decrypted value.
...
}
Is a custom Formatter
the right approach in this use case?
Or should I use a custom HandlerMethodArgumentResolver
?
Creating a Custom Type Converter in Spring MVC To create a custom type converter, we need to implement the Converter<S,T> interface. While implementing this interface we need to pass following 2 parameters to the method. S – The source object type.
In Spring MVC, the @RequestParam annotation is used to read the form data and bind it automatically to the parameter present in the provided method. So, it ignores the requirement of HttpServletRequest object to read the provided data.
Spring MVC provides one more annotation, @ModelAttributes , for binding data to the Command object. It is another way to bind the data and to customize the data binding. This annotation allows you to control the creation of the Command object.
@RequestParam is a Spring annotation used to bind a web request parameter to a method parameter. It has the following optional elements: defaultValue - used as a fallback when the request parameter is not provided or has an empty value. name - name of the request parameter to bind to.
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<Decrypt> {
@Override
public Set<Class<?>> getFieldTypes() {
Set<Class<?>> fieldTypes = new HashSet<>();
fieldTypes.add(String.class);
return Collections.unmodifiableSet(fieldTypes);
}
@Override
public Printer<String> getPrinter(Decrypt annotation, Class<?> fieldType) {
return configureFormatterFrom(annotation);
}
@Override
public Parser<String> getParser(Decrypt annotation, Class<?> fieldType) {
return configureFormatterFrom(annotation);
}
private Formatter<String> 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<String>() {
@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"));
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With